gabriel / muse public
feat patch snapshot-diff task/supercharge-snapshot-diff #1 / 1
AI Agent gabriel · 160 days ago · Apr 18, 2026 · Diff

feat(snapshot-diff): supercharge — envelope, counts, --only, --path-prefix, fix _obj bug

Fixes - Fix _obj helper in test_cmd_snapshot_diff.py: was calling write_object with bare hex OIDs (without sha256: prefix), causing validate_object_id to raise ValueError on all 20 tests that called _obj. Fix: use blob_id(). - Fix test_raw_has_no_effect_on_json_output: compared full JSON objects including elapsed_ms, which differ between two invocations. Now compares only the stable structural fields.

Source (snapshot_diff.py) - Add elapsed_ms and exit_code to _DiffResult TypedDict and all JSON outputs (single-pair and batch --stdin modes). - Add added_count, modified_count, deleted_count fields — agents no longer need to len() the arrays to read change counts. - Add --only / -o flag (choices: added|modified|deleted) — restricts output to one change category; suppressed lists are emitted as empty arrays in JSON mode; only matching status letters appear in text mode. - Add --path-prefix flag — filters diff to paths starting with PREFIX before applying --only; counts and total_changes reflect the filtered view. - Rewrite module docstring: agent quickstart, full JSON schema with new fields, --only and --path-prefix sections. - Improve docstrings on _AddedEntry, _ModifiedEntry, _DeletedEntry, _DiffResult, _resolve_to_snapshot_id, _compute_diff, _emit_text, run. - _compute_diff now accepts only and path_prefix kwargs and passes them through.

Tests (53 new in test_cmd_snapshot_diff_supercharge.py) - TestJsonEnvelope: elapsed_ms, exit_code, per-category counts match list lengths - TestJsonSchema: all fields present, sha256: prefix on IDs, entry schemas - TestOnlyFilter: all three categories, text mode, short -o flag, invalid value - TestPathPrefixFilter: src/ scoping, docs/ exclusion, nonmatching prefix, --only + --path-prefix combined - TestStdinEnvelope: elapsed_ms, count fields, --only and --path-prefix in batch - TestSecurity: ANSI in paths (text sanitized, JSON preserved), path-prefix traversal is safe (startswith filter cannot escape manifest key space) - TestDiffProperties: self-diff, symmetric add/delete, all-modified, empty snaps - TestResolution: HEAD, commit ID, branch vs snapshot ID - TestResolveToSnapshotId: direct snap, branch, HEAD, case insensitive, None cases - TestComputeDiff: unit tests for only/path_prefix kwargs - TestLargeManifestStress: 500-file diff, exact add/modify counts verified - TestConcurrentStress: 10 threads diffing same pair, all succeed

sha256:f185c5803852a6d625db5941bc684d5524cb28543dcd02cd68b780f8e912eb7d sha
+94 ~13 −2 symbols
sha256:76c3d41911bce68500e9d8134e6fe31751223ab7bb83669e9fd6cc9da38b8e75 snapshot
+94
symbols added
~13
symbols modified
−2
symbols removed
0
dead code introduced
Semantic Changes 109 symbols
+ TestComputeDiff class class TestComputeDiff L724–751
+ test_error_on_bad_ref method method test_error_on_bad_ref L747–751
+ test_only_filter_zeroes_suppressed_lists method method test_only_filter_zeroes_suppressed_lists L725–735
+ test_path_prefix_filters_entries method method test_path_prefix_filters_entries L737–745
+ TestConcurrentStress class class TestConcurrentStress L793–824
+ test_10_threads_diff_independently method method test_10_threads_diff_independently L794–824
+ TestDiffProperties class class TestDiffProperties L555–622
+ test_a_empty_all_added method method test_a_empty_all_ L602–611
+ test_all_modified_no_add_delete method method test_all_modified_no_add_delete L579–592
+ test_b_empty_all_deleted method method test_b_empty_all_deleted L613–622
+ test_both_empty_zero_changes method method test_both_empty_zero_changes L594–600
+ test_self_diff_always_zero method method test_self_diff_always_zero L556–565
+ test_symmetric_add_delete_counts method method test_symmetric_add_delete_counts L567–577
+ TestJsonEnvelope class class TestJsonEnvelope L115–180
+ test_added_count_matches_list_length method method test_added_count_matches_list_length L135–146
+ test_deleted_count_matches_list_length method method test_deleted_count_matches_list_length L159–167
+ test_elapsed_ms_present method method test_elapsed_ms_present L118–126
+ test_exit_code_zero_on_success method method test_exit_code_zero_on_success L128–133
+ test_modified_count_matches_list_length method method test_modified_count_matches_list_length L148–157
+ test_total_changes_equals_sum_of_counts method method test_total_changes_equals_sum_of_counts L169–180
+ TestJsonSchema class class TestJsonSchema L188–260
+ test_added_entry_schema method method test_added_entry_schema L216–225
+ test_all_fields_present method method test_all_fields_present L191–206
+ test_counts_are_integers method method test_counts_are_integers L252–260
+ test_deleted_entry_schema method method test_deleted_entry_schema L241–250
+ test_modified_entry_schema method method test_modified_entry_schema L227–239
+ test_snapshot_ids_are_sha256_prefixed method method test_snapshot_ids_are_sha256_prefixed L208–214
+ TestLargeManifestStress class class TestLargeManifestStress L759–785
+ test_500_file_diff_counts_correctly method method test_500_file_diff_counts_correctly L760–785
+ TestOnlyFilter class class TestOnlyFilter L268–354
+ _mixed_diff method method _mixed_diff L271–276
+ test_only_added_suppresses_modified_and_deleted method method test_only_added_suppresses_modified_and_deleted L278–288
+ test_only_added_total_changes_reflects_filter method method test_only_added_total_changes_reflects_filter L308–313
+ test_only_deleted_suppresses_added_and_modified method method test_only_deleted_suppresses_added_and_ L299–306
+ test_only_invalid_value_rejected method method test_only_invalid_value_rejected L339–345
+ test_only_modified_suppresses_added_and_deleted method method test_only_modified_suppresses_added_and_deleted L290–297
+ test_only_short_flag method method test_only_short_flag L347–354
+ test_only_text_mode_added method method test_only_text_mode_ L315–325
+ test_only_text_mode_deleted method method test_only_text_mode_deleted L327–337
+ TestPathPrefixFilter class class TestPathPrefixFilter L362–441
+ _multi_dir_diff method method _multi_dir_diff L365–378
+ test_nonmatching_prefix_yields_zero_changes method method test_nonmatching_prefix_yields_zero_changes L421–428
+ test_prefix_and_only_combined method method test_prefix_and_only_combined L430–441
+ test_prefix_counts_are_filtered method method test_prefix_counts_are_filtered L409–419
+ test_prefix_excludes_docs method method test_prefix_excludes_docs L395–407
+ test_prefix_scopes_to_src method method test_prefix_scopes_to_src L380–393
+ TestResolution class class TestResolution L630–662
+ test_branch_vs_snapshot_id method method test_branch_vs_snapshot_id L653–662
+ test_commit_id_resolution method method test_commit_id_resolution L640–651
+ test_head_vs_head_zero_changes method method test_head_vs_head_zero_changes L631–638
+ TestResolveToSnapshotId class class TestResolveToSnapshotId L670–716
+ test_head_case_insensitive method method test_head_case_insensitive L694–700
+ test_resolves_branch_name method method test_resolves_branch_name L678–684
+ test_resolves_head method method test_resolves_head L686–692
+ test_resolves_snapshot_id_directly method method test_resolves_snapshot_id_directly L671–676
+ test_returns_none_for_bad_ref method method test_returns_none_for_bad_ref L707–710
+ test_returns_none_for_head_with_no_commits method method test_returns_none_for_head_with_no_commits L712–716
+ test_returns_none_for_unknown_branch method method test_returns_none_for_unknown_branch L702–705
+ TestSecurity class class TestSecurity L507–547
+ test_ansi_in_path_not_sanitized_in_json method method test_ansi_in_path_not_sanitized_in_json L521–532
+ test_ansi_in_path_sanitized_in_text_output method method test_ansi_in_path_sanitized_in_text_output L508–519
+ test_path_prefix_cannot_escape_manifest method method test_path_prefix_cannot_escape_manifest L534–547
+ TestStdinEnvelope class class TestStdinEnvelope L449–499
+ test_batch_json_has_count_fields method method test_batch_json_has_count_fields L462–473
+ test_batch_json_has_elapsed_ms method method test_batch_json_has_elapsed_ms L452–460
+ test_batch_with_only_filter method method test_batch_with_only_filter L475–487
+ test_batch_with_path_prefix method method test_batch_with_path_prefix L489–499
+ _commit function function _commit L89–107
+ _env function function _env L66–67
+ _init_repo function function _init_repo L53–63
+ _obj function function _obj L70–73
+ _snap function function _snap L76–86
+ cli variable variable cli L42–42
+ CliRunner import import CliRunner L35–35
+ CommitRecord import import CommitRecord L39–39
+ ExitCode import import ExitCode L36–36
+ Manifest import import Manifest L40–40
+ SnapshotRecord import import SnapshotRecord L39–39
+ annotations import import annotations L26–26
+ blob_id import import blob_id L40–40
+ compute_commit_id import import compute_commit_id L38–38
+ compute_snapshot_id import import compute_snapshot_id L38–38
+ datetime import import datetime L28–28
+ json import import json L29–29
+ pathlib import import pathlib L30–30
+ pytest import import pytest L33–33
+ threading import import threading L31–31
+ write_commit import import write_commit L39–39
+ write_object import import write_object L37–37
+ write_snapshot import import write_snapshot L39–39
+ runner variable variable runner L44–44
~ muse/cli/commands/snapshot_diff.py .py 2 symbols added, 9 symbols modified
+ _ONLY_CHOICES variable variable _ONLY_CHOICES L123–123
+ time import import time L107–107
~ run
~ tests/test_cmd_snapshot_diff.py .py 1 symbol added, 2 symbols removed, 4 symbols modified
− _sha function function _sha L32–34
− hashlib import import hashlib L11–11
+ blob_id import import blob_id L21–21
~ _obj
← Older Oldest on task/supercharge-snapshot-diff
All commits
Newer → Latest on task/supercharge-snapshot-diff

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