gabriel / muse public
feat patch symlog task/symlog-phase1 #1 / 3
AI Agent gabriel · 19 days ago · Jun 28, 2026 · Diff

feat(symlog): Phase 3 — muse symlog read CLI (SL_23–SL_35)

- New command: muse/cli/commands/symlog.py - Single-symbol query with --json, --limit, --follow, --diff - --file mode: all symbols in a file - --all mode: list all symbols that have a symlog - exists subcommand: exit 0/1 with JSON schema - --operation, --author, --since, --until filters - Human text format: @{N} <new_sha12> (<old_sha12>) <when> <author> <op> - --follow traverses rename chains via born_from pointers with from_symbol annotation - --diff reconstructs symbol body from commit snapshots via lineno/end_lineno - Symbol address validation (SL_34): :: required, no empty name, no .. traversal

- app.py: register symlog command alphabetically

- 47 tests (SL_23–SL_35) all passing in tests/test_cmd_symlog_read.py

Note on argparse: nargs='?' + subparsers conflicts in Python 3.14 — fixed by using nargs='*' and dispatching 'exists' subcommand manually in run().

sha256:86d2141c349d67088a1eab9a98819d1abdc94cec3fdd0df47a3f7d26ce7dc6cf sha
+118 ~124 symbols
sha256:ef8be38856b4f8e3d1eca216ad1e7675246a723ef5d9757c71d2067c7cd1652e snapshot
+118
symbols added
~124
symbols modified
0
dead code introduced
Semantic Changes 242 symbols
~ muse/cli/commands/symlog.py .py 43 symbols added
+ _ExistsJson class class _ExistsJson L130–133
+ _NULL_NEW_LABEL variable variable _NULL_NEW_LABEL L140–140
+ _NULL_OLD_LABEL variable variable _NULL_OLD_LABEL L141–141
+ _SymlogAllJson class class _SymlogAllJson L125–127
+ _SymlogEntryJson class class _SymlogEntryJson L98–108
+ _SymlogFileJson class class _SymlogFileJson L119–122
+ _SymlogResultJson class class _SymlogResultJson L111–116
+ _apply_filters function function _apply_filters L300–325
+ _compute_diff function function _compute_diff L211–297
+ _entries_to_json function function _entries_to_json L328–356
+ _fmt_cid function function _fmt_cid L144–148
+ _fmt_entry_text function function _fmt_entry_text L151–162
+ _parse_date function function _parse_date L165–176
+ _read_with_follow function function _read_with_follow L359–400
+ _validate_symbol_addr function function _validate_symbol_addr L179–208
+ EnvelopeJson import import EnvelopeJson L75–75
+ ExitCode import import ExitCode L76–76
+ NULL_CONTENT_ID import import NULL_CONTENT_ID L78–78
+ SymlogEntry import import SymlogEntry L78–78
+ TypedDict import import TypedDict L73–73
+ annotations import import annotations L65–65
+ argparse import import argparse L67–67
+ clamp_int import import clamp_int L89–89
+ datetime import import datetime L68–68
+ difflib import import difflib L69–69
+ is_null_content_id import import is_null_content_id L78–78
+ json import import json L70–70
+ list_symlog_symbols import import list_symlog_symbols L78–78
+ list_symlog_symbols_for_file import import list_symlog_symbols_for_file L78–78
+ logging import import logging L71–71
+ long_id import import long_id L88–88
+ make_envelope import import make_envelope L75–75
+ read_symlog import import read_symlog L78–78
+ require_repo import import require_repo L77–77
+ sanitize_display import import sanitize_display L89–89
+ short_id import import short_id L88–88
+ start_timer import import start_timer L87–87
+ symlog_path import import symlog_path L78–78
+ sys import import sys L72–72
+ logger variable variable logger L91–91
+ register function function register L408–466
+ run function function run L523–691
+ run_exists function function run_exists L474–515
~ tests/test_cmd_symlog_read.py .py 74 symbols added
+ TestAddressValidation class class TestAddressValidation L589–611
+ test_empty_symbol_name_is_user_error method method test_empty_symbol_name_is_user_error L595–598
+ test_missing_separator_is_user_error method method test_missing_separator_is_user_error L590–593
+ test_path_traversal_is_user_error method method test_path_traversal_is_user_error L600–603
+ test_valid_address_does_not_error method method test_valid_address_does_not_error L605–611
+ TestAllMode class class TestAllMode L448–485
+ test_all_empty_repo method method test_all_empty_repo L481–485
+ test_all_returns_symbol_list method method test_all_returns_symbol_list L449–465
+ test_all_sorted method method test_all_sorted L467–479
+ TestAuthorFilter class class TestAuthorFilter L210–242
+ test_author_filter_case_insensitive method method test_author_filter_case_insensitive L233–242
+ test_author_filter_matches method method test_author_filter_matches L211–220
+ test_author_filter_no_match method method test_author_filter_no_match L222–231
+ TestDateFilters class class TestDateFilters L250–301
+ test_invalid_date_format_is_user_error method method test_invalid_date_format_is_user_error L295–301
+ test_since_after_until_is_user_error method method test_since_after_until_is_user_error L287–293
+ test_since_filters_old_entries method method test_since_filters_old_entries L251–261
+ test_since_includes_recent method method test_since_includes_recent L275–285
+ test_until_filters_future_entries method method test_until_filters_future_entries L263–273
+ TestDiffFlag class class TestDiffFlag L364–396
+ test_diff_absent_without_flag method method test_diff_absent_without_flag L387–396
+ test_diff_null_for_created_entry method method test_diff_null_for_created_entry L365–373
+ test_diff_present_for_modified_entry method method test_diff_present_for_modified_entry L375–385
+ TestExists class class TestExists L493–528
+ test_exists_exit_0_when_present method method test_exists_exit_0_when_present L512–517
+ test_exists_exit_1_when_absent method method test_exists_exit_1_when_absent L519–522
+ test_exists_false_when_no_entries method method test_exists_false_when_no_entries L504–510
+ test_exists_json_exit_code_field method method test_exists_json_exit_code_field L524–528
+ test_exists_true_when_entries_present method method test_exists_true_when_entries_present L494–502
+ TestFileMode class class TestFileMode L404–440
+ test_file_mode_each_symbol_has_schema method method test_file_mode_each_symbol_has_schema L426–434
+ test_file_mode_empty_path method method test_file_mode_empty_path L436–440
+ test_file_mode_returns_all_symbols method method test_file_mode_returns_all_symbols L405–424
+ TestFollow class class TestFollow L309–356
+ test_follow_includes_prior_symbol_entries method method test_follow_includes_prior_symbol_entries L317–332
+ test_follow_true_in_json method method test_follow_true_in_json L310–315
+ test_from_symbol_none_for_own_entries method method test_from_symbol_none_for_own_entries L347–356
+ test_from_symbol_populated_for_prior_entries method method test_from_symbol_populated_for_prior_entries L334–345
+ TestHumanText class class TestHumanText L536–581
+ test_human_text_created_shows_initial method method test_human_text_created_shows_initial L544–550
+ test_human_text_deleted_shows_deleted_label method method test_human_text_deleted_shows_deleted_label L552–566
+ test_human_text_header_line method method test_human_text_header_line L575–581
+ test_human_text_shows_at_n_prefix method method test_human_text_shows_at_n_prefix L537–542
+ test_human_text_shows_operation method method test_human_text_shows_operation L568–573
+ TestIntegrationFiltersAndFollow class class TestIntegrationFiltersAndFollow L619–705
+ test_follow_stitches_two_symbols_newest_first method method test_follow_stitches_two_symbols_newest_first L672–705
+ test_four_commit_filter_combination method method test_four_commit_filter_combination L620–670
+ TestJsonSchema class class TestJsonSchema L72–131
+ test_all_fields_present_on_empty_log method method test_all_fields_present_on_empty_log L73–87
+ test_content_ids_are_long_form method method test_content_ids_are_long_form L122–131
+ test_entry_schema_all_fields_present method method test_entry_schema_all_fields_present L89–106
+ test_followed_false_when_no_follow_flag method method test_followed_false_when_no_follow_flag L108–113
+ test_followed_true_when_follow_flag method method test_followed_true_when_follow_flag L115–120
+ TestLimit class class TestLimit L139–157
+ test_default_limit_is_20 method method test_default_limit_is_20 L154–157
+ test_limit_caps_entry_list method method test_limit_caps_entry_list L140–152
+ TestOperationFilter class class TestOperationFilter L165–202
+ test_operation_filter_case_insensitive method method test_operation_filter_case_insensitive L181–190
+ test_operation_filter_no_match method method test_operation_filter_no_match L192–202
+ test_operation_filter_substring method method test_operation_filter_substring L166–179
+ _commit function function _commit L45–55
+ _init_repo function function _init_repo L34–36
+ _invoke function function _invoke L25–31
+ _make_repo function function _make_repo L58–60
+ _symlog function function _symlog L63–64
+ _write_file function function _write_file L39–42
+ CliRunner import import CliRunner L15–15
+ annotations import import annotations L6–6
+ json import import json L8–8
+ os import import os L9–9
+ pathlib import import pathlib L10–10
+ pytest import import pytest L13–13
+ textwrap import import textwrap L11–11
+ runner variable variable runner L17–17

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