gabriel / muse public
feat patch task/shelf #3 / 3
gabriel · 158 days ago · Apr 16, 2026 · Diff

feat: add muse shelf — first-class named working-tree checkpoints

Replaces the old stash concept with a ground-up redesign:

- Full snapshots (not deltas): unambiguous restoration after merges - Content-addressed IDs: sha256 of entry content (minus id field) - Already-current detection: files merged since shelving counted separately, not silently re-written (fixes the stash silent-absorption bug) - Intent-typed: interrupt | checkpoint | handoff | experiment | draft - Agent-attributed: created_by, resumable, tags for multi-agent handoff - GC-integrated: shelf objects treated as reachable - Programmatic API: _shelf_push_programmatic / _shelf_pop_programmatic for autoshelf support in checkout and merge

CLI: muse shelf save / list / read / apply / pop / drop / diff

154 tests passing covering unit, integration, E2E, stress, data-integrity, performance, security, and docstrings.

sha256:f3acd2fcb9463f22c913b627fb94122fb5b3da02a745a8dc6e33b2652d6c7ef3 sha
+255 ~117 symbols
sha256:d136271c629a390ffca4260d6b19b3c14899bd7cadf2612622f7069b8025cf7e snapshot
+255
symbols added
~117
symbols modified
0
dead code introduced
Semantic Changes 372 symbols
~ muse/cli/commands/shelf.py .py 51 symbols added
+ ShelfEntry class class ShelfEntry L107–136
+ _SHELF_FILE variable variable _SHELF_FILE L101–101
+ _SHELF_MAX_BYTES variable variable _SHELF_MAX_BYTES L100–100
+ _VALID_INTENT_TYPES variable variable _VALID_INTENT_TYPES L102–102
+ _apply_shelf_snapshot function function _apply_shelf_snapshot L313–358
+ _compute_shelf_id function function _compute_shelf_id L144–152
+ _entry_files_count function function _entry_files_count L361–367
+ _generate_name function function _generate_name L291–305
+ _load_shelf function function _load_shelf L155–214
+ _resolve_entry function function _resolve_entry L241–288
+ _save_shelf function function _save_shelf L217–238
+ _shelf_pop_programmatic function function _shelf_pop_programmatic L476–515
+ _shelf_push_programmatic function function _shelf_push_programmatic L375–473
+ _verify_snapshot_objects function function _verify_snapshot_objects L308–310
+ ExitCode import import ExitCode L86–86
+ Manifest import import Manifest L85–85
+ TypedDict import import TypedDict L83–83
+ annotations import import annotations L72–72
+ apply_manifest import import apply_manifest L97–97
+ argparse import import argparse L74–74
+ assert_not_symlink import import assert_not_symlink L96–96
+ compute_snapshot_id import import compute_snapshot_id L90–90
+ datetime import import datetime L75–75
+ directories_from_manifest import import directories_from_manifest L90–90
+ get_head_snapshot_manifest import import get_head_snapshot_manifest L95–95
+ has_object import import has_object L87–87
+ hashlib import import hashlib L76–76
+ is_ignored import import is_ignored L89–89
+ json import import json L77–77
+ load_ignore_patterns import import load_ignore_patterns L90–90
+ logging import import logging L78–78
+ os import import os L79–79
+ pathlib import import pathlib L80–80
+ read_current_branch import import read_current_branch L95–95
+ read_object import import read_object L87–87
+ read_repo_id import import read_repo_id L88–88
+ require_repo import import require_repo L88–88
+ resolve_plugin import import resolve_plugin L98–98
+ sanitize_display import import sanitize_display L96–96
+ sys import import sys L81–81
+ tempfile import import tempfile L82–82
+ write_object_from_path import import write_object_from_path L87–87
+ logger variable variable logger L104–104
+ register function function register L523–759
+ run_apply function function run_apply L1084–1151
+ run_diff function function run_diff L1275–1343
+ run_drop function function run_drop L1227–1272
+ run_list function function run_list L916–996
+ run_pop function function run_pop L1154–1224
+ run_read function function run_read L999–1081
+ run_save function function run_save L767–913
~ tests/test_cmd_shelf.py .py 203 symbols added
+ TestApplyJsonSchema class class TestApplyJsonSchema L869–913
+ test_apply_empty_exits_1 method method test_apply_empty_exits_1 L902–904
+ test_apply_preserves_shelf_entry method method test_apply_preserves_shelf_entry L886–900
+ test_apply_restores_file method method test_apply_restores_file L906–913
+ test_schema_complete method method test_schema_complete L872–878
+ test_status_applied method method test_status_applied L880–884
+ TestApplyShelfSnapshot class class TestApplyShelfSnapshot L448–499
+ test_already_current_not_rewritten method method test_already_current_not_rewritten L460–470
+ test_deleted_already_gone_is_idempotent method method test_deleted_already_gone_is_idempotent L482–488
+ test_deleted_paths_removed method method test_deleted_paths_ L472–480
+ test_mixed_restored_and_already_current method method test_mixed_restored_and_already_current L490–499
+ test_restored_count_correct method method test_restored_count_correct L449–458
+ TestComputeShelfId class class TestComputeShelfId L184–223
+ test_different_content_different_id method method test_different_content_different_id L207–211
+ test_id_is_64_hex_after_prefix method method test_id_is_64_hex_after_prefix L193–199
+ test_id_stable_across_calls method method test_id_stable_across_calls L219–223
+ test_id_starts_with_sha256 method method test_id_starts_with_sha256 L187–191
+ test_same_content_same_id method method test_same_content_same_id L201–205
+ test_snapshot_diff_changes_id method method test_snapshot_diff_changes_id L213–217
+ TestDataIntegrity class class TestDataIntegrity L1314–1379
+ test_already_current_detection method method test_already_current_detection L1315–1332
+ test_content_address_id_matches_recomputed method method test_content_address_id_matches_recomputed L1346–1356
+ test_deleted_paths_tracked method method test_deleted_paths_tracked L1358–1372
+ test_snapshot_contains_all_tracked_files method method test_snapshot_contains_all_tracked_files L1334–1344
+ test_snapshot_id_is_sha256_prefixed method method test_snapshot_id_is_sha256_prefixed L1374–1379
+ TestDiffJsonSchema class class TestDiffJsonSchema L1018–1064
+ test_diff_does_not_modify_workdir method method test_diff_does_not_modify_workdir L1036–1042
+ test_diff_empty_exits_1 method method test_diff_empty_exits_1 L1044–1046
+ test_diff_lists_already_current_when_merged method method test_diff_lists_already_current_when_merged L1048–1064
+ test_schema_complete method method test_schema_complete L1021–1027
+ test_would_restore_has_changed_files method method test_would_restore_has_changed_files L1029–1034
+ TestDocstrings class class TestDocstrings L1615–1654
+ test_module_docstring method method test_module_docstring L1616–1618
+ test_run_apply_docstring method method test_run_apply_docstring L1632–1634
+ test_run_diff_docstring method method test_run_diff_docstring L1644–1646
+ test_run_drop_docstring method method test_run_drop_docstring L1640–1642
+ test_run_list_docstring method method test_run_list_docstring L1624–1626
+ test_run_pop_docstring method method test_run_pop_docstring L1636–1638
+ test_run_read_docstring method method test_run_read_docstring L1628–1630
+ test_run_save_docstring method method test_run_save_docstring L1620–1622
+ test_shelf_pop_programmatic_docstring method method test_shelf_pop_programmatic_docstring L1652–1654
+ test_shelf_push_programmatic_docstring method method test_shelf_push_programmatic_docstring L1648–1650
+ TestDropJsonSchema class class TestDropJsonSchema L965–1010
+ test_drop_does_not_restore_file method method test_drop_does_not_restore_file L993–999
+ test_drop_empty_exits_1 method method test_drop_empty_exits_1 L989–991
+ test_drop_removes_entry_from_list method method test_drop_removes_entry_from_list L1001–1010
+ test_id_is_sha256 method method test_id_is_sha256 L982–987
+ test_schema_complete method method test_schema_complete L968–974
+ test_status_dropped method method test_status_dropped L976–980
+ TestGenerateName class class TestGenerateName L231–262
+ test_branch_with_special_chars_sanitized method method test_branch_with_special_chars_sanitized L241–246
+ test_empty_branch_fallback method method test_empty_branch_fallback L248–251
+ test_first_entry_is_000 method method test_first_entry_is_000 L232–234
+ test_increments_when_conflict method method test_increments_when_conflict L236–239
+ test_large_n_zero_padded method method test_large_n_zero_p L258–262
+ test_zero_padded_to_three_digits method method test_zero_padded_to_three_digits L253–256
+ TestListJsonSchema class class TestListJsonSchema L733–803
+ test_empty_returns_empty_array method method test_empty_returns_empty_array L748–753
+ test_files_count_positive method method test_files_count_positive L755–760
+ test_filter_branch method method test_filter_branch L762–770
+ test_filter_branch_no_match_empty method method test_filter_branch_no_match_empty L772–777
+ test_filter_by_creator method method test_filter_by_creator L794–803
+ test_filter_resumable method method test_filter_resumable L779–792
+ test_schema_complete method method test_schema_complete L739–746
+ TestLoadSaveShelf class class TestLoadSaveShelf L270–380
+ test_assert_not_symlink_in_load method method test_assert_not_symlink_in_load L365–367
+ test_fsync_called_in_save method method test_fsync_called_in_save L361–363
+ test_load_empty_when_no_file method method test_load_empty_when_no_file L271–274
+ test_load_ignores_malformed_json method method test_load_ignores_malformed_json L331–335
+ test_load_ignores_non_list_json method method test_load_ignores_non_list_json L337–341
+ test_load_ignores_oversized_file method method test_load_ignores_oversized_file L324–329
+ test_load_skips_entries_without_snapshot method method test_load_skips_entries_without_snapshot L343–350
+ test_load_skips_non_dict_entries method method test_load_skips_non_dict_entries L352–359
+ test_multiple_entries_ordered method method test_multiple_entries_ordered L369–380
+ test_roundtrip_preserves_all_fields method method test_roundtrip_preserves_all_fields L288–315
+ test_save_creates_shelf_json method method test_save_creates_shelf_json L276–286
+ test_save_is_atomic_no_temp_files method method test_save_is_atomic_no_temp_files L317–322
+ TestNameIndexResolution class class TestNameIndexResolution L1072–1116
+ _save_n method method _save_n L1073–1082
+ test_drop_by_name method method test_drop_by_name L1100–1106
+ test_out_of_range_exits_1 method method test_out_of_range_exits_1 L1108–1111
+ test_pop_by_index method method test_pop_by_index L1092–1098
+ test_pop_by_name method method test_pop_by_name L1084–1090
+ test_unknown_name_exits_1 method method test_unknown_name_exits_1 L1113–1116
+ TestObjectIntegrity class class TestObjectIntegrity L1124–1152
+ _corrupt_object method method _corrupt_object L1125–1131
+ test_apply_with_missing_object_exits_3 method method test_apply_with_missing_object_exits_3 L1139–1143
+ test_drop_succeeds_even_with_missing_objects method method test_drop_succeeds_even_with_missing_objects L1145–1152
+ test_pop_with_missing_object_exits_3 method method test_pop_with_missing_object_exits_3 L1133–1137
+ TestPerformance class class TestPerformance L1387–1414
+ test_list_50_entries_under_2s method method test_list_50_entries_under_2s L1399–1414
+ test_save_pop_under_5s method method test_save_pop_under_5s L1388–1397
+ TestPopJsonSchema class class TestPopJsonSchema L921–957
+ test_pop_empty_exits_1 method method test_pop_empty_exits_1 L944–946
+ test_pop_removes_entry method method test_pop_removes_entry L948–957
+ test_schema_complete method method test_schema_complete L924–930
+ test_shelf_size_after_decremented method method test_shelf_size_after_decremented L938–942
+ test_status_popped method method test_status_popped L932–936
+ TestProgrammaticApi class class TestProgrammaticApi L1160–1222
+ test_pop_by_name method method test_pop_by_name L1216–1222
+ test_pop_empty_raises method method test_pop_empty_raises L1204–1207
+ test_pop_removes_from_registry method method test_pop_removes_from_registry L1209–1214
+ test_pop_returns_entry method method test_pop_returns_entry L1198–1202
+ test_push_clean_returns_none method method test_push_clean_returns_none L1168–1172
+ test_push_duplicate_name_raises method method test_push_duplicate_name_raises L1191–1196
+ test_push_returns_entry method method test_push_returns_entry L1161–1166
+ test_push_with_metadata method method test_push_with_metadata L1174–1189
+ TestReadJsonSchema class class TestReadJsonSchema L811–861
+ test_files_is_list_of_strings method method test_files_is_list_of_strings L826–832
+ test_read_by_index method method test_read_by_index L848–853
+ test_read_by_name method method test_read_by_name L834–846
+ test_read_empty_exits_1 method method test_read_empty_exits_1 L855–857
+ test_read_unknown_name_exits_1 method method test_read_unknown_name_exits_1 L859–861
+ test_schema_complete method method test_schema_complete L818–824
+ TestRegisterFlags class class TestRegisterFlags L528–603
+ _parse method method _parse L529–534
+ test_apply_entry_arg method method test_apply_entry_arg L585–587
+ test_diff_entry_arg method method test_diff_entry_arg L601–603
+ test_drop_entry_arg method method test_drop_entry_arg L581–583
+ test_list_branch_filter method method test_list_branch_filter L589–591
+ test_list_by_filter method method test_list_by_filter L597–599
+ test_list_resumable_filter method method test_list_resumable_filter L593–595
+ test_pop_entry_arg method method test_pop_entry_arg L573–575
+ test_pop_entry_default_none method method test_pop_entry_default_none L577–579
+ test_save_intent_default_none method method test_save_intent_default_none L544–546
+ test_save_intent_long method method test_save_intent_long L540–542
+ test_save_intent_short method method test_save_intent_short L536–538
+ test_save_intent_type_default method method test_save_intent_type_default L548–550
+ test_save_intent_type_handoff method method test_save_intent_type_handoff L552–554
+ test_save_json_shorthand method method test_save_json_shorthand L569–571
+ test_save_resumable_default_false method method test_save_resumable_default_false L560–562
+ test_save_resumable_flag method method test_save_resumable_flag L556–558
+ test_save_tag_repeatable method method test_save_tag_repeatable L564–567
+ TestResolveEntry class class TestResolveEntry L388–440
+ _entries method method _entries L389–396
+ test_empty_list_raises method method test_empty_list_raises L419–422
+ test_integer_string_resolves method method test_integer_string_resolves L405–410
+ test_name_lookup_exact method method test_name_lookup_exact L412–417
+ test_negative_index_raises method method test_negative_index_raises L430–434
+ test_none_returns_default_0 method method test_none_returns_default_0 L398–403
+ test_out_of_range_raises method method test_out_of_range_raises L424–428
+ test_unknown_name_raises method method test_unknown_name_raises L436–440
+ TestRoundTrips class class TestRoundTrips L1230–1306
+ test_named_save_then_pop_by_name method method test_named_save_then_pop_by_name L1296–1306
+ test_save_apply_apply_idempotent method method test_save_apply_apply_idempotent L1243–1258
+ test_save_drop_no_restore method method test_save_drop_no_restore L1260–1267
+ test_save_pop_restores_content method method test_save_pop_restores_content L1231–1241
+ test_shelf_persists_across_commands method method test_shelf_persists_across_commands L1287–1294
+ test_stack_ordering_newest_first method method test_stack_ordering_newest_first L1269–1285
+ TestSaveJsonSchema class class TestSaveJsonSchema L611–725
+ test_duplicate_name_exits_1 method method test_duplicate_name_exits_1 L706–713
+ test_files_count_positive method method test_files_count_positive L639–643
+ test_id_is_sha256 method method test_id_is_sha256 L632–637
+ test_intent_default_null method method test_intent_default_null L645–649
+ test_intent_type_custom method method test_intent_type_custom L664–669
+ test_intent_type_default_checkpoint method method test_intent_type_default_checkpoint L658–662
+ test_intent_with_flag method method test_intent_with_flag L651–656
+ test_named_save method method test_named_save L699–704
+ test_nothing_to_shelf_schema_complete method method test_nothing_to_shelf_schema_complete L687–697
+ test_resumable_flag_stored method method test_resumable_flag_stored L671–676
+ test_schema_complete method method test_schema_complete L618–624
+ test_shelf_size_increments method method test_shelf_size_increments L715–725
+ test_status_shelved method method test_status_shelved L626–630
+ test_tags_stored method method test_tags_stored L678–685
+ TestSecurity class class TestSecurity L1422–1506
+ test_ansi_in_branch_name_sanitized method method test_ansi_in_branch_name_sanitized L1433–1443
+ test_ansi_in_file_path_sanitized_in_read method method test_ansi_in_file_path_sanitized_in_read L1456–1467
+ test_ansi_in_intent_sanitized method method test_ansi_in_intent_sanitized L1445–1454
+ test_invalid_format_exits_1 method method test_invalid_format_exits_1 L1469–1471
+ test_oversized_shelf_json_ignored method method test_oversized_shelf_json_ignored L1473–1477
+ test_snapshot_values_must_be_strings method method test_snapshot_values_must_be_strings L1479–1506
+ test_symlink_at_shelf_json_returns_empty method method test_symlink_at_shelf_json_returns_empty L1423–1431
+ TestStress class class TestStress L1514–1607
+ test_100_save_drop_cycles method method test_100_save_drop_cycles L1515–1533
+ test_concurrent_save_to_isolated_repos method method test_concurrent_save_to_isolated_repos L1563–1593
+ test_save_load_large_snapshot method method test_save_load_large_snapshot L1595–1607
+ test_stack_with_50_entries_then_clear method method test_stack_with_50_entries_then_clear L1535–1561
+ TestVerifySnapshotObjects class class TestVerifySnapshotObjects L502–520
+ test_all_present_returns_empty method method test_all_present_returns_empty L503–508
+ test_empty_snapshot_returns_empty method method test_empty_snapshot_returns_empty L517–520
+ test_missing_object_returned method method test_missing_object_returned L510–515
+ _env function function _env L54–55
+ _init_repo function function _init_repo L58–74
+ _make_commit function function _make_commit L77–106
+ _make_shelf_entry function function _make_shelf_entry L120–147
+ _write_object function function _write_object L109–117
+ cli variable variable cli L45–45
+ Any import import Any L40–40
+ CliRunner import import CliRunner L43–43
+ annotations import import annotations L29–29
+ argparse import import argparse L31–31
+ datetime import import datetime L32–32
+ inspect import import inspect L33–33
+ json import import json L34–34
+ os import import os L35–35
+ pathlib import import pathlib L36–36
+ pytest import import pytest L42–42
+ threading import import threading L37–37
+ time import import time L38–38
+ uuid import import uuid L39–39
+ repo function function repo L156–166
+ runner variable variable runner L46–46
+ shelved_repo function function shelved_repo L170–176

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