gabriel / muse public
feat minor dev
AI Agent gabriel · 149 days ago · Apr 9, 2026 · Diff

feat: add muse hub issue get/list/close/reopen/comment subcommands with security hardening

- Five new subcommands: get, list, close, reopen, comment - Input validation: label length (_MAX_ISSUE_LABEL_LEN=255), body length (_MAX_ISSUE_COMMENT_LEN=50000) - URL encoding via urllib.parse.quote for all user-supplied query params - sanitize_display on all API-returned strings in text output - Idempotency guards on close/reopen in musehub service layer - 445 tests passing (8 new test classes, ~90 new tests)

sha256:6688898fe43753e724d54c7d51207d3aeeac274f7d6ee399fd3e28c4c31faa38 sha
+105 ~4 symbols
sha256:0bf0d3e0b15867e4833af0e5f167f6ea735e8adfdaad5bbea1861bb3b19044c4 snapshot
+105
symbols added
~4
symbols modified
0
dead code introduced
Semantic Changes 109 symbols
~ muse/cli/commands/hub.py .py 7 symbols added, 3 symbols modified
+ _MAX_ISSUE_COMMENT_LEN variable variable _MAX_ISSUE_COMMENT_LEN L130–130
+ _MAX_ISSUE_LABEL_LEN variable variable _MAX_ISSUE_LABEL_LEN L129–129
+ run_issue_close function function run_issue_close L2128–2161
+ run_issue_comment function function run_issue_comment L2200–2248
+ run_issue_get function function run_issue_get L2016–2052
+ run_issue_list function function run_issue_list L2055–2125
+ run_issue_reopen function function run_issue_reopen L2164–2197
~ tests/test_cmd_hub_hardening.py .py 98 symbols added, 1 symbol modified
+ TestIssueCloseHardening class class TestIssueCloseHardening L6369–6491
+ test_help_shows_exit_codes method method test_help_shows_exit_codes L6489–6491
+ test_json_short_flag method method test_json_short_flag L6416–6424
+ test_negative_number_exits_nonzero_no_network method method test_negative_number_exits_nonzero_no_network L6383–6392
+ test_path_contains_close_and_number method method test_path_contains_close_and_number L6452–6475
+ test_success_json_output_has_state_closed method method test_success_json_output_has_state_closed L6403–6414
+ test_success_text_mode_exit_zero method method test_success_text_mode_exit_zero L6394–6401
+ test_text_output_goes_to_stderr method method test_text_output_goes_to_stderr L6477–6487
+ test_uses_post_method method method test_uses_post_method L6426–6450
+ test_zero_number_exits_nonzero_no_network method method test_zero_number_exits_nonzero_no_network L6372–6381
+ TestIssueCommentHardening class class TestIssueCommentHardening L6628–6913
+ test_body_at_max_length_accepted method method test_body_at_max_length_accepted L6854–6868
+ test_body_sent_in_request_payload method method test_body_sent_in_request_payload L6722–6749
+ test_body_sent_verbatim_at_max_length method method test_body_sent_verbatim_at_max_length L6884–6913
+ test_body_too_long_error_message_mentions_length method method test_body_too_long_error_message_mentions_length L6870–6882
+ test_body_too_long_exits_nonzero_no_network method method test_body_too_long_exits_nonzero_no_network L6838–6852
+ test_empty_body_exits_nonzero_no_network method method test_empty_body_exits_nonzero_no_network L6657–6668
+ test_help_shows_exit_codes method method test_help_shows_exit_codes L6834–6836
+ test_json_short_flag method method test_json_short_flag L6710–6720
+ test_missing_body_flag_required method method test_missing_body_flag_required L6683–6691
+ test_negative_number_exits_nonzero_no_network method method test_negative_number_exits_nonzero_no_network L6644–6655
+ test_path_contains_comments_and_number method method test_path_contains_comments_and_number L6778–6803
+ test_success_json_output_has_comments method method test_success_json_output_has_comments L6693–6708
+ test_text_output_goes_to_stderr method method test_text_output_goes_to_stderr L6805–6817
+ test_text_shows_comment_count method method test_text_shows_comment_count L6819–6832
+ test_uses_post_method method method test_uses_post_method L6751–6776
+ test_whitespace_only_body_exits_nonzero method method test_whitespace_only_body_exits_nonzero L6670–6681
+ test_zero_number_exits_nonzero_no_network method method test_zero_number_exits_nonzero_no_network L6631–6642
+ TestIssueGetHardening class class TestIssueGetHardening L5882–6019
+ test_ansi_in_author_sanitized method method test_ansi_in_author_sanitized L5984–5994
+ test_ansi_in_title_sanitized method method test_ansi_in_title_sanitized L5971–5982
+ test_invalid_number_message_mentions_positive method method test_invalid_number_message_mentions_positive L5908–5916
+ test_json_output_contains_number_and_title method method test_json_output_contains_number_and_title L5918–5930
+ test_json_passthrough_does_not_emit_stderr_summary method method test_json_passthrough_does_not_emit_stderr_summary L6007–6019
+ test_json_short_flag method method test_json_short_flag L5932–5941
+ test_negative_number_exits_nonzero_no_network method method test_negative_number_exits_nonzero_no_network L5897–5906
+ test_open_state_shows_correct_icon method method test_open_state_shows_correct_icon L5996–6005
+ test_text_output_goes_to_stderr_not_stdout method method test_text_output_goes_to_stderr_not_stdout L5943–5956
+ test_text_shows_number_title_author method method test_text_shows_number_title_author L5958–5969
+ test_zero_number_exits_nonzero_no_network method method test_zero_number_exits_nonzero_no_network L5885–5895
+ TestIssueListHardening class class TestIssueListHardening L6027–6361
+ test_ansi_in_number_field_sanitized method method test_ansi_in_number_field_sanitized L6182–6194
+ test_ansi_in_title_field_sanitized method method test_ansi_in_title_field_sanitized L6196–6206
+ test_empty_list_exits_zero method method test_empty_list_exits_zero L6052–6059
+ test_empty_list_json_is_empty_array method method test_empty_list_json_is_empty_array L6061–6069
+ test_invalid_state_value_rejected_by_argparse method method test_invalid_state_value_rejected_by_argparse L6234–6244
+ test_json_output_is_array method method test_json_output_is_array L6030–6040
+ test_json_short_flag method method test_json_short_flag L6042–6050
+ test_label_at_max_length_accepted method method test_label_at_max_length_accepted L6272–6297
+ test_label_injection_does_not_add_extra_query_params method method test_label_injection_does_not_add_extra_query_params L6126–6155
+ test_label_too_long_error_message_mentions_length method method test_label_too_long_error_message_mentions_length L6299–6309
+ test_label_too_long_exits_nonzero_no_network method method test_label_too_long_exits_nonzero_no_network L6258–6270
+ test_label_url_encoded_in_request method method test_label_url_encoded_in_request L6097–6124
+ test_limit_passed_as_per_page method method test_limit_passed_as_per_page L6157–6180
+ test_no_issues_message_sanitizes_state method method test_no_issues_message_sanitizes_state L6338–6361
+ test_state_default_is_open method method test_state_default_is_open L6208–6232
+ test_state_param_encoded_in_request method method test_state_param_encoded_in_request L6071–6095
+ test_state_url_encoded_in_request method method test_state_url_encoded_in_request L6311–6336
+ test_text_output_goes_to_stderr method method test_text_output_goes_to_stderr L6246–6256
+ TestIssueReopenHardening class class TestIssueReopenHardening L6499–6620
+ test_help_shows_exit_codes method method test_help_shows_exit_codes L6618–6620
+ test_json_short_flag method method test_json_short_flag L6546–6554
+ test_negative_number_exits_nonzero_no_network method method test_negative_number_exits_nonzero_no_network L6513–6522
+ test_path_contains_reopen_and_number method method test_path_contains_reopen_and_number L6581–6604
+ test_success_json_output_has_state_open method method test_success_json_output_has_state_open L6533–6544
+ test_success_text_mode_exit_zero method method test_success_text_mode_exit_zero L6524–6531
+ test_text_output_goes_to_stderr method method test_text_output_goes_to_stderr L6606–6616
+ test_uses_post_method method method test_uses_post_method L6556–6579
+ test_zero_number_exits_nonzero_no_network method method test_zero_number_exits_nonzero_no_network L6502–6511
+ TestNewSubcommandsE2E class class TestNewSubcommandsE2E L6995–7094
+ test_close_then_reopen_flow method method test_close_then_reopen_flow L7025–7043
+ test_comment_agent_pipeline method method test_comment_agent_pipeline L7045–7058
+ test_full_crud_sequence method method test_full_crud_sequence L7060–7094
+ test_get_agent_pipeline method method test_get_agent_pipeline L6998–7009
+ test_list_agent_pipeline method method test_list_agent_pipeline L7011–7023
+ TestNewSubcommandsRegistration class class TestNewSubcommandsRegistration L6921–6987
+ test_all_five_subcommands_present method method test_all_five_subcommands_present L6984–6987
+ test_close_help_shows_exit_codes method method test_close_help_shows_exit_codes L6960–6962
+ test_close_in_issue_help method method test_close_in_issue_help L6932–6934
+ test_comment_b_alias method method test_comment_b_alias L6972–6982
+ test_comment_help_shows_body_flag method method test_comment_help_shows_body_flag L6968–6970
+ test_comment_in_issue_help method method test_comment_in_issue_help L6940–6942
+ test_get_help_shows_quickstart method method test_get_help_shows_quickstart L6944–6946
+ test_get_in_issue_help method method test_get_in_issue_help L6924–6926
+ test_list_help_shows_label_flag method method test_list_help_shows_label_flag L6952–6954
+ test_list_help_shows_limit_flag method method test_list_help_shows_limit_flag L6956–6958
+ test_list_help_shows_state_flag method method test_list_help_shows_state_flag L6948–6950
+ test_list_in_issue_help method method test_list_in_issue_help L6928–6930
+ test_reopen_help_shows_exit_codes method method test_reopen_help_shows_exit_codes L6964–6966
+ test_reopen_in_issue_help method method test_reopen_in_issue_help L6936–6938
+ TestNewSubcommandsStress class class TestNewSubcommandsStress L7102–7191
+ test_concurrent_comment_body_validation method method test_concurrent_comment_body_validation L7130–7149
+ test_concurrent_number_validation method method test_concurrent_number_validation L7110–7128
+ test_issue_list_resp_helper_is_stable method method test_issue_list_resp_helper_is_stable L7151–7167
+ test_list_label_encoding_many_special_chars method method test_list_label_encoding_many_special_chars L7169–7185
+ test_zero_and_negative_numbers_all_rejected method method test_zero_and_negative_numbers_all_rejected L7187–7191
+ _comment_list_resp function function _comment_list_resp L4000–4006
+ _issue_list_resp function function _issue_list_resp L3994–3997
← Older Oldest on dev
All commits
Newer → Latest on dev

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