From d10bb2ab6fb9e16028070bf7075284a7b0476872 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Sun, 13 Sep 2026 06:27:46 -0700 Subject: [PATCH] test: make tests/ mirror the source tree; drop issue numbers from filenames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `scripts/run_tests.sh tests//` is how a change gets its regression coverage run, so a test filed under the wrong directory is a test nobody runs when that code changes. Two kinds of drift had accumulated. Parallel directories for one source package, folded into the mirror: tests/acp -> tests/acp_adapter (its __init__/conftest move with it) tests/cli -> tests/hermes_cli (prompt_toolkit fixture merged into hermes_cli/conftest.py) tests/run_agent -> tests/agent (backoff fixture becomes agent/conftest.py) tests/relay -> tests/gateway/relay tests/state -> tests/hermes_state 246 loose files at tests/ root, routed by the package they import/patch: hermes_cli, hermes_state, agent, gateway, tools, plugins, tui_gateway, cron. Installer and desktop-update script tests go to tests/scripts/{install, desktop_update}/. 43 tests of root-level modules (batch_runner, utils, hermes_constants, packaging) stay at the root. Filenames drop their issue numbers (95 files: test_89315_x.py -> test_x.py); the number stays in the module docstring where it has context. Collisions: test_cli_skin_integration.py existed in both tests/ and tests/cli with different subsets — merged into one (10 tests, all kept); run_agent/test_pre_compress_memory_context.py -> agent/..._handoff.py; tests/test_account_usage.py -> agent/test_account_usage_fetch.py; tests/test_web_server.py -> hermes_cli/test_web_server_ws_ping.py. Deleted: test_minisweagent_path.py (empty since PR #2804), test_model_picker_scroll.py (tested a private copy of the logic, imported nothing), test_process_loop_event_loop_warning.py (asserted asyncio behaviour, imported nothing from Hermes). Repo-root path arithmetic (Path(__file__).parents[N], dirname chains) is bumped for the 202 files that changed depth and verified by evaluating every such expression against the new location. classify_changes' desktop-updater lane prefix, tests-os.yml's ignore glob and every in-tree path comment follow the moves. tests/test_tests_tree_layout.py keeps the tree from drifting back. --- .github/workflows/tests-os.yml | 4 +- AGENTS.md | 7 +- agent/activity_tracking.py | 2 +- agent/agent_runtime_helpers.py | 4 +- cli.py | 2 +- hermes_cli/cli_info_mixin.py | 2 +- hermes_cli/default_soul.py | 2 +- hermes_state_portability.py | 2 +- scripts/ci/classify_changes.py | 2 +- scripts/desktop-update/posix.sh | 2 +- scripts/run_tests.sh | 2 +- scripts/run_tests_parallel.py | 4 +- tests/{acp => acp_adapter}/__init__.py | 0 tests/{acp => acp_adapter}/conftest.py | 2 +- .../test_approval_isolation.py | 2 +- tests/{acp => acp_adapter}/test_auth.py | 0 .../test_edit_approval.py | 0 .../test_empty_session_persistence.py | 0 tests/{acp => acp_adapter}/test_entry.py | 0 tests/{acp => acp_adapter}/test_events.py | 0 tests/{acp => acp_adapter}/test_mcp_e2e.py | 0 .../test_named_provider_catalogs.py | 0 .../{acp => acp_adapter}/test_permissions.py | 0 .../test_ping_suppression.py | 0 tests/{acp => acp_adapter}/test_server.py | 0 tests/{acp => acp_adapter}/test_session.py | 0 .../test_session_db_private_access.py | 0 .../test_session_provenance.py | 0 tests/{acp => acp_adapter}/test_tools.py | 0 tests/{run_agent => agent}/conftest.py | 2 +- .../repro_image_shrink_brick.py} | 4 +- .../test_402_not_retried.py} | 0 .../test_413_compression.py | 0 tests/{run_agent => agent}/test_860_dedup.py | 0 .../test_account_usage_fetch.py} | 0 .../test_agent_guardrails.py | 0 .../test_anthropic_mid_tool_call_drop.py | 0 .../test_anthropic_prompt_cache_policy.py | 0 .../test_anthropic_response_header_capture.py | 0 .../test_anthropic_stream_pool_cleanup.py} | 0 .../test_anthropic_third_party_oauth_guard.py | 0 .../test_anthropic_truncation_continuation.py | 0 .../test_api_max_retries_config.py | 0 .../test_async_httpx_del_neuter.py | 0 .../test_auth_provider_failover.py | 0 .../test_authorization_gate.py | 0 ...iliary_client_base_url_host_validation.py} | 0 .../test_auxiliary_user_default_headers.py | 2 +- .../test_background_review.py | 0 .../test_background_review_cache_parity.py | 0 .../test_background_review_cost_controls.py | 0 .../test_background_review_input_budget.py | 0 .../test_background_review_list_shapes.py | 2 +- .../test_background_review_summary.py | 0 ...t_background_review_toolset_restriction.py | 0 tests/{ => agent}/test_bitwarden_secrets.py | 2 +- .../test_callable_api_key.py | 0 ...pt_6600.py => test_cascading_interrupt.py} | 0 .../test_codex_app_server_compaction.py | 0 .../test_codex_app_server_integration.py | 0 .../test_codex_app_server_lifecycle.py | 0 .../test_codex_multimodal_tool_result.py | 0 .../test_codex_no_tools_nonetype.py | 0 .../test_codex_sdk_transform_bypass.py | 0 .../test_codex_silent_hang_hint.py | 0 .../test_codex_xai_oauth_recovery.py | 0 .../{ => agent}/test_command_secret_source.py | 2 +- ...st_commit_memory_session_context_engine.py | 0 .../test_compaction_prompt_rebuild.py | 0 .../test_compress_context_fallback_shim.py | 0 .../test_compress_focus_plugin_fallback.py | 0 .../test_compression_abort_state_reset.py | 0 .../test_compression_boundary.py | 0 .../test_compression_boundary_hook.py | 0 .../test_compression_budget_rearm.py | 0 .../test_compression_budget_refund.py | 0 .../test_compression_closed_adoption.py | 0 ...8.py => test_compression_count_warning.py} | 0 .../test_compression_feasibility.py | 0 .../test_compression_lock_defer.py | 2 +- .../test_compression_persistence.py | 0 ...ew_76354.py => test_compression_review.py} | 0 ....py => test_compression_stall_fallback.py} | 0 ...py => test_compression_stall_interrupt.py} | 0 .../test_compression_timeout_terminal.py | 0 ..._compression_trigger_excludes_reasoning.py | 0 ...y => test_compression_worker_isolation.py} | 0 .../test_compressor_fallback_update.py | 0 .../test_concurrent_interrupt.py | 0 .../test_content_policy_blocked.py} | 0 .../test_context_overflow_loop.py} | 0 .../test_context_token_tracking.py | 0 .../test_continuation_ceiling_wedge.py | 0 .../test_continuation_repetition_guard.py | 0 .../test_conversation_fallback_state.py | 0 tests/{ => agent}/test_copilot_initiator.py | 0 .../test_copilot_native_vision_headers.py | 0 .../test_corruption_recovery_guidance.py | 0 ...eate_openai_client_disables_sdk_retries.py | 0 ...t_create_openai_client_kwargs_isolation.py | 0 .../test_create_openai_client_proxy_env.py | 0 .../test_create_openai_client_reuse.py | 0 .../test_create_openai_client_ssl_verify.py | 0 .../test_credential_pool_auto_detect.py} | 0 .../test_credential_pool_interrupt.py | 0 ...test_credential_rotation_route_settings.py | 0 .../test_credits_notices_toggle.py | 0 ... => test_cron_inflight_prompt_reappend.py} | 0 ..._62151.py => test_cron_inline_api_call.py} | 0 .../test_cross_process_turn_lease.py | 0 tests/{ => agent}/test_ctx_halving_fix.py | 0 ...st_custom_provider_extra_headers_client.py | 0 .../test_deepseek_reasoning_content_echo.py | 0 .../test_deepseek_v4_thinking_live.py | 2 +- .../test_dict_tool_call_args.py | 0 .../test_direct_contexts_stream_inline.py | 0 .../test_dropped_tool_call_recovery.py | 0 ...est_empty_response_recovery_persistence.py | 0 .../test_empty_terminal_reasoning_surface.py | 0 .../test_entitlement_fail_closed.py | 0 .../test_env_credential_turn_refresh.py | 0 .../test_env_loader_applied_homes.py | 0 .../test_env_loader_secret_sources.py | 2 +- ...rity_84371.py => test_estimator_parity.py} | 0 .../test_exit_cleanup_interrupt.py | 0 .../test_export_current_turn_boundary.py | 0 .../test_fallback_429_after_timeout.py} | 0 .../test_fallback_api_mode_preservation.py | 0 .../test_fallback_credential_isolation.py | 0 .../test_fallback_exhaustion_cooldown.py} | 0 .../test_fallback_reasoning_override.py | 0 .../test_file_mutation_verifier.py | 0 .../test_fireworks_live.py | 2 +- .../test_first_chunk_at_hook.py | 0 .../test_flush_diverts_on_corrupt_state_db.py | 0 .../test_gemini_native_reservation.py | 0 .../test_hermetic_side_effect_guards.py | 0 .../test_identity_flush.py | 0 .../test_image_corrupt_recovery.py} | 2 +- .../test_image_generate_parallel.py | 0 .../test_image_rejection_fallback.py | 0 .../test_image_shrink_recovery.py | 0 .../test_in_place_commit_rollback.py} | 0 .../test_in_place_compaction.py | 0 .../test_in_place_persist_marker.py} | 0 .../test_infinite_compaction_loop.py | 0 .../test_init_fallback_on_exhausted_pool.py | 0 .../test_interactive_interrupt.py | 0 .../test_interpreter_shutdown_turn_exit.py | 0 .../test_interrupt_propagation.py | 0 .../test_invalid_context_length_warning.py | 0 tests/{ => agent}/test_iron_proxy.py | 2 +- tests/{ => agent}/test_iron_proxy_e2e.py | 0 .../test_iteration_budget_race.py | 0 .../test_jsondecodeerror_retryable.py | 0 .../test_last_reasoning_per_turn.py | 0 ...length_continuation_thinking_exhaustion.py | 0 .../test_lmstudio_load_mode.py | 0 ....py => test_load_time_durability_stamp.py} | 0 .../test_long_context_tier_429.py | 0 .../test_malformed_tool_arguments.py | 0 .../test_materialize_data_url_cleanup.py | 0 .../test_memory_nudge_counter_hydration.py | 0 .../test_memory_provider_init.py | 0 .../test_memory_sync_interrupted.py | 2 +- tests/{ => agent}/test_message_reactions.py | 0 .../test_message_sequence_repair.py | 0 tests/agent/test_meta_agent_init.py | 2 +- ..._66793.py => test_moa_cold_start_cache.py} | 0 .../test_moa_fanout_cadence.py | 0 .../test_moa_loop_mode.py | 0 .../test_moa_prepared_request_leak.py} | 0 .../test_moa_privacy_filter.py | 0 .../test_moa_streaming.py | 0 .../test_model_streaming_config.py | 0 .../test_multimodal_interim.py} | 0 .../test_multimodal_tool_content_recovery.py | 0 .../test_native_compaction.py | 0 ...est_native_compaction_nontext_retention.py | 0 ...est_native_compaction_summary_retention.py | 0 ...t_native_compaction_switch_capabilities.py | 0 .../test_nonretryable_error_html_summary.py | 0 .../{run_agent => agent}/test_notice_spine.py | 2 +- .../test_nous_429_fallback_reentry.py | 0 .../test_nous_fallback_unavailable.py | 0 tests/{ => agent}/test_ollama_num_ctx.py | 0 tests/{ => agent}/test_onepassword_secrets.py | 2 +- .../test_openai_client_lifecycle.py | 0 .../test_opencode_free_client_headers.py | 0 .../test_outer_error_retry_bound.py} | 0 tests/{ => agent}/test_output_cap_parsing.py | 0 .../test_overflow_overhead_aware_tokens.py | 0 .../test_overflow_partial_terminal.py} | 0 .../test_partial_stream_finish_reason.py | 0 .../test_per_model_compression_threshold.py | 0 .../test_per_model_threshold_init_ordering.py | 0 .../test_percentage_clamp.py | 0 .../test_plugin_context_engine_init.py | 0 .../test_plugin_stream_hooks.py | 0 .../test_post_tool_compression_attempt_cap.py | 0 ...st_pre_compress_memory_context_handoff.py} | 0 .../test_preflight_compression_cap_e2e.py | 0 tests/agent/test_preflight_lock_defer.py | 2 +- .../test_preflight_timeout_typed_result.py | 0 .../test_primary_runtime_restore.py | 0 .../test_private_credential_writers.py | 0 .../test_proactive_prune_loop_wiring.py | 0 ...932.py => test_protected_tail_pressure.py} | 0 .../test_provider_attribution_headers.py | 0 .../test_provider_fallback.py | 0 .../test_provider_parity.py | 0 .../test_reasoning_echo_resolver_e2e.py | 0 tests/{ => agent}/test_redaction_registry.py | 0 ..._repair_reanchors_current_turn_user_idx.py | 0 .../test_repair_tool_call_arguments.py | 0 .../test_repair_tool_call_name.py | 0 .../test_request_client_reuse_abort_races.py | 0 .../test_reset_aware_primary_restore.py | 0 .../test_retry_status_buffer.py | 0 tests/{ => agent}/test_retry_utils.py | 0 .../test_review_prompt_class_first.py | 0 ...test_rotation_flush_persisted_boundary.py} | 0 tests/{run_agent => agent}/test_run_agent.py | 0 .../test_run_agent_codex_responses.py | 0 .../test_run_agent_multimodal_prologue.py | 0 .../test_sanitiser_escalation.py | 0 .../test_sequential_chats_live.py | 2 +- .../test_sequential_tool_timeout.py | 0 .../test_session_activity_persist.py | 0 .../test_session_id_env.py | 0 .../test_session_meta_filtering.py | 0 .../test_session_reset_fix.py | 0 ...est_session_rotation_flush_cold_resume.py} | 0 .../test_session_source.py | 0 .../test_shared_client_fd_corruption.py} | 0 .../test_skill_applied_notifications.py | 0 ...ore_65429.py => test_skip_memory_store.py} | 0 tests/{ => agent}/test_sql_injection.py | 0 .../test_start_order_gate.py | 0 tests/{run_agent => agent}/test_steer.py | 0 .../test_stream_drop_logging.py | 0 .../test_stream_interrupt_retry.py | 0 .../test_stream_single_writer.py} | 0 .../test_stream_stale_breaker_reset.py | 4 +- .../test_stream_stale_circuit_breaker.py | 2 +- .../test_streamed_text_accumulation.py | 0 tests/{run_agent => agent}/test_streaming.py | 0 .../test_streaming_tool_call_repair.py | 0 .../test_strict_api_validation.py | 0 .../test_strip_reasoning_tags_cli.py | 0 .../test_summarize_api_error.py | 0 .../test_switch_model_bedrock_sigv4.py | 0 .../test_switch_model_context.py | 0 .../test_switch_model_fallback_prune.py | 0 .../test_switch_model_pool_reload.py} | 0 .../test_switch_model_reapplies_headers.py | 0 .../test_switch_model_reasoning_override.py | 0 .../test_switch_model_rollback.py | 0 .../test_switch_model_stale_base_url.py | 0 ...test_text_turn_incremental_persistence.py} | 0 .../test_thinking_only_sanitizer.py | 0 .../test_thinking_prefill_trailing_turn.py | 0 .../test_thinking_sig_recovery_persistence.py | 0 .../test_tls_fd_recycle_corruption.py | 0 .../test_token_persistence_non_cli.py | 0 .../test_tool_activity_heartbeat.py | 0 .../test_tool_arg_coercion.py | 0 .../test_tool_batch_segmentation.py | 0 .../test_tool_call_args_sanitizer.py | 0 .../test_tool_call_guardrail_runtime.py | 0 .../test_tool_call_incremental_persistence.py | 0 ...st_tool_executor_contextvar_propagation.py | 0 .../test_tool_name_db_persistence.py | 0 .../test_turn_completion_explainer.py | 2 +- .../test_turn_liveness_watchdog.py | 0 .../test_unicode_ascii_codec.py | 0 .../test_verification_continuation_budget.py | 0 .../test_vision_aware_preprocessing.py | 0 ...outing_31179.py => test_vision_routing.py} | 0 .../test_vision_tool_messages.py | 0 .../test_wait_state_visibility.py | 0 tests/ci/test_classify_changes.py | 8 +- tests/cli/__init__.py | 0 tests/cli/conftest.py | 50 ------- tests/cli/test_cli_skin_integration.py | 89 ------------ tests/conftest.py | 4 +- ..._62151.py => test_cron_direct_api_call.py} | 0 ...all_87644.py => test_cron_emfile_stall.py} | 0 .../test_cron_manage_profile_scope.py | 0 ...1.py => test_cron_run_stale_claim_reap.py} | 0 ...ss.py => test_cronjob_gateway_liveness.py} | 0 tests/{ => cron}/test_estop.py | 0 ...rge_80624.py => test_jobs_shrink_merge.py} | 0 ...er_stall_60703.py => test_ticker_stall.py} | 0 tests/{ => cron}/test_timezone.py | 0 .../relay/test_relay_format_hints.py | 0 .../relay/test_relay_inchannel_continuable.py | 0 .../test_relay_prompt_ack_stream_isolation.py | 0 ...xt.py => test_auto_reset_clean_context.py} | 0 ...=> test_auto_reset_evicts_cached_agent.py} | 0 ...p_off_loop.py => test_cleanup_off_loop.py} | 0 ...=> test_compression_interrupt_demotion.py} | 0 ...sage.py => test_duplicate_user_message.py} | 0 .../test_empty_model_fallback.py | 0 ... => test_env_override_explicit_disable.py} | 0 ...derr.py => test_faulthandler_no_stderr.py} | 0 .../test_gateway_streaming_nested_config.py | 0 ...y => test_internal_notification_marker.py} | 0 ...nd_dedup.py => test_media_resend_dedup.py} | 0 ...reset.py => test_memory_flush_on_reset.py} | 0 ... => test_model_switch_after_auto_reset.py} | 0 ... => test_multiplex_primary_token_scope.py} | 0 ...est_priority_path_compression_demotion.py} | 0 .../test_profile_isolation_runtime.py | 0 .../test_replace_ownership_guard.py} | 0 ...dlock.py => test_reset_button_deadlock.py} | 0 ...ty_82616.py => test_session_continuity.py} | 0 ...n_11016.py => test_session_split_brain.py} | 0 ...est_shutdown_inflight_transcript_flush.py} | 0 ...382.py => test_silent_partial_delivery.py} | 0 .../test_slack_thread_require_mention.py | 0 ...ode.py => test_stale_base_url_api_mode.py} | 0 ...n_30170.py => test_subagent_protection.py} | 0 ...9.py => test_telegram_closewait_limits.py} | 0 ...=> test_telegram_fallback_pool_release.py} | 0 ...est_telegram_prune_stale_topic_binding.py} | 0 ... test_telegram_topic_profile_isolation.py} | 0 ...=> test_telegram_topic_profile_routing.py} | 0 tests/{ => gateway}/test_tini_shim.py | 2 +- ...788.py => test_transcript_read_failure.py} | 0 ...y => test_transient_failure_transcript.py} | 0 ...eview_76354.py => test_watchdog_review.py} | 0 ...> test_whatsapp_multiplex_secret_scope.py} | 0 .../{ => gateway}/test_yuanbao_integration.py | 2 +- tests/{ => gateway}/test_yuanbao_markdown.py | 6 +- tests/{ => gateway}/test_yuanbao_pipeline.py | 2 +- tests/{ => gateway}/test_yuanbao_proto.py | 2 +- .../test_yuanbao_reconnect_set_active.py | 2 +- tests/{ => gateway}/test_yuanbao_shutdown.py | 2 +- tests/hermes_cli/conftest.py | 46 +++++++ .../test_active_session_exclusivity.py | 0 .../test_audio_playback_guard.py | 2 +- .../hermes_cli/test_auth_toctou_file_modes.py | 2 +- .../test_bang_shell_mode.py | 0 .../test_bracketed_paste_timeout.py | 0 .../test_branch_command.py | 0 .../test_busy_input_mode_command.py | 0 .../test_chat_q_exit_clear.py | 0 .../test_cli_active_agent_ref_wiring.py | 0 .../test_cli_approval_ui.py | 0 .../test_cli_async_delegation_delivery.py | 0 .../test_cli_background_busy_path.py | 2 +- .../test_cli_background_status_indicator.py | 0 .../test_cli_background_tui_refresh.py | 0 .../test_cli_bracketed_paste_sanitizer.py | 0 .../test_cli_browser_connect.py | 0 .../test_cli_clarify_batch.py | 2 +- .../test_cli_cmd_backspace.py | 0 .../test_cli_codex_context_reference.py | 0 .../test_cli_context_warning.py | 0 .../test_cli_copy_command.py | 0 .../test_cli_delegate_background_notice.py | 0 .../test_cli_extension_hooks.py | 0 .../test_cli_external_editor.py | 0 .../{cli => hermes_cli}/test_cli_file_drop.py | 0 .../test_cli_first_run_setup.py | 0 .../test_cli_force_redraw.py | 0 .../test_cli_goal_interrupt.py | 0 .../test_cli_goal_kick_prompt.py | 0 .../test_cli_goal_parked_resume.py | 0 .../test_cli_image_command.py | 0 tests/{cli => hermes_cli}/test_cli_init.py | 2 +- .../test_cli_insights_command.py | 0 .../test_cli_interrupt_ack_race.py | 0 .../test_cli_interrupt_drain_regression.py | 0 .../test_cli_interrupt_subagent.py | 0 .../test_cli_light_mode.py | 0 .../test_cli_loading_indicator.py | 0 .../test_cli_manual_compress.py | 0 .../test_cli_markdown_rendering.py | 0 .../test_cli_mcp_config_watch.py | 0 .../test_cli_new_session.py | 2 +- ...900.py => test_cli_partial_update_hint.py} | 0 .../{cli => hermes_cli}/test_cli_pet_pane.py | 0 .../test_cli_prefix_matching.py | 0 .../test_cli_preloaded_skills.py | 0 .../test_cli_provider_resolution.py | 0 .../test_cli_queue_paste.py | 0 .../test_cli_quiet_stdout_leak.py | 0 .../test_cli_reload_skills.py | 0 .../test_cli_resume_command.py | 0 tests/{cli => hermes_cli}/test_cli_retry.py | 0 .../test_cli_save_config_value.py | 0 .../test_cli_secret_capture.py | 0 .../test_cli_session_store_shared_registry.py | 0 .../test_cli_shift_enter_newline.py | 0 .../test_cli_shutdown_memory_messages.py | 0 .../test_cli_skin_integration.py | 17 +++ .../test_cli_status_bar.py | 0 .../test_cli_status_bar_goal.py | 0 .../test_cli_status_command.py | 0 .../test_cli_steer_busy_path.py | 0 .../test_cli_terminal_response_sanitizer.py | 0 .../test_cli_terminal_shortcuts.py | 0 .../test_cli_tools_command.py | 0 .../test_cli_user_message_preview.py | 0 .../test_cli_yolo_resume_persistence.py | 0 .../test_cli_yolo_toggle.py | 0 tests/{ => hermes_cli}/test_code_skew.py | 0 .../test_command_palette.py | 0 .../test_compat_manifest_targets.py | 2 +- .../test_completion_backlog.py | 0 .../test_compress_flags.py | 0 .../test_compress_focus.py | 0 .../{cli => hermes_cli}/test_compress_here.py | 0 .../test_compress_type_ahead.py | 0 .../test_cpr_local_leak.py | 0 .../test_cprint_bg_thread.py | 0 .../test_ctrl_enter_newline.py | 0 .../test_cwd_env_respect.py | 0 .../test_destructive_slash_confirm.py | 0 .../test_destructive_slash_inline_skip_e2e.py | 0 .../test_empty_session_hygiene.py | 0 ...4107.py => test_ensure_hermes_home_uid.py} | 0 .../test_env_loader_op_bootstrap.py | 2 +- .../test_exit_delete_session.py | 0 .../test_exit_summary_resume_hint.py | 0 .../test_exit_watchdog_signal_arm.py | 0 .../{cli => hermes_cli}/test_fast_command.py | 0 .../test_finite_chat_delegation.py | 0 tests/{cli => hermes_cli}/test_focus_view.py | 0 tests/{ => hermes_cli}/test_gitlock.py | 0 ...obal_switch_persists_base_url_api_mode.py} | 0 .../test_handoff_cleanup_race.py | 0 .../test_handoff_slow_dispatch_timeout.py | 0 .../test_indicator_command.py | 0 ...est_interrupt_output_history_regression.py | 0 tests/{ => hermes_cli}/test_iron_proxy_cli.py | 0 .../test_kitty_keypad_parity.py | 0 .../test_lazy_secrets_dispatch.py | 4 +- .../test_lazy_secrets_import.py | 4 +- ...test_list_recent_user_messages_handoffs.py | 0 .../test_manual_compress.py | 0 .../test_minimax_model_validation.py | 0 tests/{ => hermes_cli}/test_minimax_oauth.py | 0 tests/{cli => hermes_cli}/test_moa_command.py | 0 ...612.py => test_model_alias_credentials.py} | 0 .../test_model_picker_filter.py | 0 ..._87189.py => test_model_prefix_routing.py} | 0 .../test_modify_other_keys_aliases.py | 0 .../test_oneshot_resumed_session_persist.py | 0 .../test_partial_compress.py | 0 .../test_personality_none.py | 0 .../test_plugin_compat_notice.py | 4 +- .../test_prefill_config.py | 0 .../test_prepend_note_to_message.py | 0 .../{cli => hermes_cli}/test_prompt_stash.py | 0 .../test_prompt_stash_cli.py | 4 +- .../test_prompt_text_input_thread_safety.py | 0 .../{ => hermes_cli}/test_pty_keepalive_ws.py | 0 tests/{ => hermes_cli}/test_pty_session.py | 0 .../test_quick_commands.py | 0 .../test_quiet_turn_author.py | 0 .../test_reasoning_command.py | 0 ...regression_16767.py => test_regression.py} | 2 + .../{ => hermes_cli}/test_resource_limits.py | 0 .../test_resume_display.py | 0 .../test_resume_model_restore.py | 0 .../test_resume_quiet_stderr.py | 0 .../test_save_conversation_location.py | 0 .../test_session_boundary_hooks.py | 0 .../test_session_detail_malformed_db.py | 0 .../test_session_export_batch.py | 0 .../test_session_search_context_batch.py | 0 .../test_session_skill_previews.py | 0 .../test_session_system_prompt_dedup.py | 0 .../test_session_vacuum_config.py | 2 +- .../test_show_config_credential.py | 0 .../test_single_query_clarify.py | 0 .../test_single_query_session_finalize.py | 0 .../test_slash_command_interrupt.py | 0 .../test_slash_confirm_windows.py | 0 .../test_slash_dispatch_table.py | 0 .../test_slash_undo_title_robustness.py | 0 ...87189.py => test_startup_model_routing.py} | 0 .../test_status_bar_git_branch.py | 0 ...est_status_lines_held_during_stream_box.py | 0 .../test_steer_inline_repaint.py} | 0 .../test_stream_delta_think_tag.py | 0 .../test_stream_flush_left.py | 0 .../test_stream_partial_line_flush.py | 0 .../test_subagent_dock_collapse.py | 0 .../test_subagent_dock_surface.py | 0 .../test_subagent_monitor.py | 0 .../test_subagent_monitor_prompts.py | 0 .../test_subagent_monitor_resize.py | 0 .../test_subagent_monitor_ui.py | 0 .../test_subagent_notification_display.py | 0 .../test_subagent_shortcut.py | 0 .../test_terminal_interrupt_recovery.py | 0 ...en_81521.py => test_terminal_io_broken.py} | 0 .../test_termios_drift_heal.py | 0 .../test_tool_gen_start_dedupe.py | 0 .../test_tool_progress_scrollback.py | 0 .../test_transformed_stream_output.py | 0 .../test_tui_rapid_enter_paste.py | 0 .../test_tui_terminal_reset_on_exit.py | 0 .../test_update_command.py | 0 .../test_version_command.py | 0 .../test_vim_mode_command.py | 0 .../test_web_server_sessiondb_eventloop.py | 0 .../test_web_server_status_topology_cache.py | 0 .../test_web_server_ws_ping.py} | 0 ...y => test_windows_gateway_job_teardown.py} | 0 tests/{cli => hermes_cli}/test_worktree.py | 0 .../test_worktree_pushed_tier.py | 0 .../test_worktree_security.py | 0 .../test_worktree_selfheal.py | 0 .../test_worktree_sync_base.py | 0 .../test_append_message_after_close.py} | 0 ...stamp.py => test_automatic_ended_stamp.py} | 0 ...est_background_review_session_isolation.py | 0 ...ompression_lease_refresh_before_publish.py | 0 .../test_compression_lineage_guard.py | 0 .../test_compression_watermark_commit.py | 0 .../test_conftest_wal_gate.py | 0 .../test_dedupe_migration_contention.py | 0 .../test_delegate_cascade.py} | 0 .../test_disk_full_error.py | 0 ...> test_empty_sweep_archived_transcript.py} | 0 .../{ => hermes_state}/test_fts_cjk_bigram.py | 2 +- .../test_fts_fresh_bootstrap_admission.py | 0 .../test_fts_holder_instance_scope.py | 0 .../test_fts_index_fail_open.py | 0 .../test_fts_orphan_shadow_repair.py | 0 .../test_fts_rebuild_admission.py | 0 .../test_fts_runtime_rebuild.py | 0 .../test_fts_tool_write_bounds.py | 0 .../test_fts_trigram_cron_exclusion.py | 0 .../test_fts_trigram_subagent_exclusion.py | 0 .../test_fts_update_of_narrowing.py | 0 .../test_guest_durability_barriers.py | 0 tests/{ => hermes_state}/test_hermes_state.py | 0 ...est_hermes_state_compression_busy_retry.py | 0 .../test_hermes_state_compression_locks.py | 0 .../test_hermes_state_conn_lock_audit.py | 2 +- .../test_hermes_state_ids.py | 0 .../test_hermes_state_readonly_preflight.py | 0 .../test_hermes_state_wal_fallback.py | 0 .../test_journal_mode_config.py | 0 .../test_journal_mode_upgrade_warning.py | 0 .../test_no_locked_readers_gate.py | 0 .../test_no_more_rows_retry.py | 0 ....py => test_orphan_sweep_cross_backend.py} | 0 .../test_readonly_fts_decode_error.py} | 0 .../test_schema_read_probe.py | 0 .../test_search_slow_query_log.py | 0 .../test_session_db_context_manager.py | 0 .../test_session_db_profile_stamp.py | 0 .../test_session_db_read_conn_pool.py | 0 .../test_session_db_read_path_split.py | 0 .../test_session_delete_profile_isolation.py | 0 .../test_session_git_metadata_generation.py | 0 .../test_session_model_usage_pk_heal.py | 0 .../test_session_turn_lease.py | 0 .../test_session_workspace_binding.py | 0 .../test_sqlite_lock_safe_inspection.py | 0 .../test_sqlite_wal_reset_gate.py | 0 ...t_stale_tool_call_marker_session_repair.py | 0 ...st_state_db_fts_segment_collision_probe.py | 0 .../test_state_db_holders.py | 0 .../test_state_db_lock_fail_closed.py | 0 .../test_state_db_malformed_repair.py | 0 .../test_state_db_notadb_fail_closed.py | 0 .../test_state_db_repair_live_writer_guard.py | 0 .../test_state_db_repair_loop_cap.py | 0 .../test_state_db_repair_loop_mtime.py | 0 .../test_state_db_repair_non_destructive.py | 0 .../{ => hermes_state}/test_state_db_stats.py | 0 .../test_state_db_wal_unlink_race.py | 0 .../test_state_db_write_durability.py | 0 .../test_state_synchronous_pragma.py | 4 +- ...ntics.py => test_tail_rewind_semantics.py} | 0 .../test_wal_checkpoint_strategy.py | 0 ...terplay.py => test_watermark_interplay.py} | 0 .../test_write_lock_patience.py | 0 .../test_writer_conn_thread_safety.py | 0 .../test_zeroed_state_db.py | 0 tests/perf_guards/test_pattern_b_scaling.py | 2 +- .../model_providers/test_deepseek_profile.py | 2 +- .../test_honcho_client_concurrency.py | 0 .../test_honcho_client_config.py | 0 .../test_honcho_session_context.py | 0 .../test_honcho_startup_fail_open.py | 0 tests/{ => plugins}/test_plugin_storage.py | 0 tests/{ => plugins}/test_plugin_utils.py | 0 .../test_plugins_manage_profile_scope.py | 0 .../test_secret_scope_plugin_families.py | 0 .../test_telegram_polling_progress_ptb.py | 0 ...transform_api_error_classification_hook.py | 0 .../test_transform_llm_output_hook.py | 0 .../test_transform_tool_result_hook.py | 0 tests/run_agent/__init__.py | 0 .../test_desktop_update_linux_gate.py | 2 +- ...est_desktop_update_shim_profile_cleanup.py | 2 +- .../test_desktop_update_shim_progress.py | 2 +- .../test_desktop_update_tcc_heal.py | 2 +- .../test_desktop_update_windows_cwd.py | 2 +- .../test_desktop_update_windows_pipe_drain.py | 2 +- .../test_desktop_update_windows_progress.py | 2 +- ...t_desktop_update_windows_python_handoff.py | 2 +- ...est_desktop_update_windows_retry_policy.py | 2 +- .../test_desktop_update_windows_timestamp.py | 2 +- ...test_desktop_update_windows_ui_delivery.py | 2 +- ...est_install_autostash_conflict_recovery.py | 2 +- .../test_install_clone_throttle_fallback.py | 2 +- .../test_install_commit_pin_rollback.py | 2 +- .../install}/test_install_diverged_update.py | 2 +- .../install}/test_install_lockfile_churn.py | 2 +- .../install}/test_install_macos_launcher.py | 2 +- .../test_install_no_initial_commit.py | 2 +- .../install}/test_install_ps1_ascii_only.py | 2 +- .../test_install_ps1_browser_install.py | 6 +- .../test_install_ps1_managed_node_swap.py | 2 +- ...t_install_ps1_managed_python_provenance.py | 2 +- .../test_install_ps1_native_stderr_eap.py | 2 +- .../test_install_ps1_node_path_for_npm.py | 2 +- .../test_install_ps1_python_fallback_venv.py | 2 +- .../test_install_ps1_resolver_strictmode.py | 2 +- .../test_install_ps1_uv_install_fallback.py | 2 +- .../test_install_ps1_uv_powershell_host.py | 2 +- .../test_install_ps1_venv_process_tree.py | 2 +- .../test_install_ps1_venv_recreate_safety.py | 2 +- .../test_install_ps1_venv_rename_abort.py | 2 +- ...t_install_ps1_venv_transaction_boundary.py | 4 +- ...est_install_ps1_web_server_syntax_probe.py | 2 +- .../test_install_scripts_computer_use.py | 2 +- .../install}/test_install_sh_acp_launcher.py | 2 +- .../test_install_sh_bootstrap_marker.py | 2 +- .../test_install_sh_browser_install.py | 2 +- .../test_install_sh_install_method_stamp.py | 2 +- .../test_install_sh_node_deps_failure.py | 2 +- .../test_install_sh_node_deps_workspaces.py | 2 +- .../test_install_sh_node_global_prefix.py | 2 +- .../test_install_sh_node_npm_check.py | 2 +- .../test_install_sh_node_prerelease.py | 2 +- .../install/test_install_sh_node_probe.py} | 2 +- ...test_install_sh_node_tarball_without_xz.py | 2 +- ...test_install_sh_pythonpath_sanitization.py | 2 +- .../test_install_sh_reuse_supported_python.py | 2 +- ...test_install_sh_root_fhs_uv_python_path.py | 2 +- .../test_install_sh_setup_wizard_tty_probe.py | 2 +- .../install}/test_install_sh_symlink_stomp.py | 2 +- .../test_install_sh_termux_network_prereqs.py | 2 +- .../test_install_sh_termux_python_bounds.py | 2 +- .../test_install_sh_uv_lock_config.py | 2 +- .../install}/test_install_unmerged_index.py | 2 +- .../{ => scripts}/test_run_tests_parallel.py | 16 +-- .../test_run_tests_parallel_stdio.py | 2 +- tests/{ => skills}/test_evidence_store.py | 2 +- tests/test_minisweagent_path.py | 2 - tests/test_model_picker_scroll.py | 100 -------------- tests/test_process_loop_event_loop_warning.py | 130 ------------------ tests/test_tests_tree_layout.py | 76 ++++++++++ ...999.py => test_yaml_indent_consistency.py} | 0 tests/{ => tools}/test_browser_vault.py | 2 +- .../test_compaction_tool_refresh.py | 2 +- ...tf8_68053.py => test_hub_lock_non_utf8.py} | 0 tests/{ => tools}/test_model_tools.py | 0 .../{ => tools}/test_plugin_compat_warning.py | 4 +- tests/{ => tools}/test_plugin_skills.py | 0 tests/tools/test_pre_transcription_hook.py | 2 +- .../test_subprocess_home_isolation.py | 0 ...ain.py => test_timeout_transport_drain.py} | 0 .../{ => tools}/test_toolset_distributions.py | 0 tests/{ => tools}/test_toolsets.py | 0 .../test_voice_max_recording_seconds.py | 0 ...heartbeat.py => test_backend_heartbeat.py} | 0 .../tui_gateway/test_entry_picker_prewarm.py | 2 +- .../test_lazy_session_regressions.py | 0 ...n.py => test_routing_provenance_reopen.py} | 2 +- .../test_slash_worker_watchdog.py | 0 .../test_tui_entry_mcp_owner.py | 0 .../test_tui_gateway_event_replay.py | 0 .../test_tui_gateway_loop_noise.py | 0 .../test_tui_gateway_queue_on_busy.py | 0 .../test_tui_gateway_server.py | 0 .../test_tui_gateway_server_crash_history.py | 0 .../{ => tui_gateway}/test_tui_gateway_ws.py | 0 .../test_tui_mcp_late_refresh.py | 0 .../test_ws_keepalive_config.py | 0 website/docs/developer-guide/acp-internals.md | 2 +- .../docs/developer-guide/adding-providers.md | 10 +- .../docs/developer-guide/egress-internals.md | 10 +- .../developer-guide/memory-provider-plugin.md | 2 +- .../docs/developer-guide/provider-runtime.md | 2 +- website/docs/user-guide/cli.md | 2 +- 697 files changed, 312 insertions(+), 537 deletions(-) rename tests/{acp => acp_adapter}/__init__.py (100%) rename tests/{acp => acp_adapter}/conftest.py (96%) rename tests/{acp => acp_adapter}/test_approval_isolation.py (98%) rename tests/{acp => acp_adapter}/test_auth.py (100%) rename tests/{acp => acp_adapter}/test_edit_approval.py (100%) rename tests/{acp => acp_adapter}/test_empty_session_persistence.py (100%) rename tests/{acp => acp_adapter}/test_entry.py (100%) rename tests/{acp => acp_adapter}/test_events.py (100%) rename tests/{acp => acp_adapter}/test_mcp_e2e.py (100%) rename tests/{acp => acp_adapter}/test_named_provider_catalogs.py (100%) rename tests/{acp => acp_adapter}/test_permissions.py (100%) rename tests/{acp => acp_adapter}/test_ping_suppression.py (100%) rename tests/{acp => acp_adapter}/test_server.py (100%) rename tests/{acp => acp_adapter}/test_session.py (100%) rename tests/{acp => acp_adapter}/test_session_db_private_access.py (100%) rename tests/{acp => acp_adapter}/test_session_provenance.py (100%) rename tests/{acp => acp_adapter}/test_tools.py (100%) rename tests/{run_agent => agent}/conftest.py (96%) rename tests/{run_agent/repro_48013_image_shrink_brick.py => agent/repro_image_shrink_brick.py} (98%) rename tests/{run_agent/test_31273_402_not_retried.py => agent/test_402_not_retried.py} (100%) rename tests/{run_agent => agent}/test_413_compression.py (100%) rename tests/{run_agent => agent}/test_860_dedup.py (100%) rename tests/{test_account_usage.py => agent/test_account_usage_fetch.py} (100%) rename tests/{run_agent => agent}/test_agent_guardrails.py (100%) rename tests/{run_agent => agent}/test_anthropic_mid_tool_call_drop.py (100%) rename tests/{run_agent => agent}/test_anthropic_prompt_cache_policy.py (100%) rename tests/{run_agent => agent}/test_anthropic_response_header_capture.py (100%) rename tests/{run_agent/test_28161_anthropic_stream_pool_cleanup.py => agent/test_anthropic_stream_pool_cleanup.py} (100%) rename tests/{run_agent => agent}/test_anthropic_third_party_oauth_guard.py (100%) rename tests/{run_agent => agent}/test_anthropic_truncation_continuation.py (100%) rename tests/{run_agent => agent}/test_api_max_retries_config.py (100%) rename tests/{run_agent => agent}/test_async_httpx_del_neuter.py (100%) rename tests/{run_agent => agent}/test_auth_provider_failover.py (100%) rename tests/{run_agent => agent}/test_authorization_gate.py (100%) rename tests/agent/{test_auxiliary_client_base_url_host_validation_52608.py => test_auxiliary_client_base_url_host_validation.py} (100%) rename tests/{run_agent => agent}/test_background_review.py (100%) rename tests/{run_agent => agent}/test_background_review_cache_parity.py (100%) rename tests/{run_agent => agent}/test_background_review_cost_controls.py (100%) rename tests/{run_agent => agent}/test_background_review_input_budget.py (100%) rename tests/{ => agent}/test_background_review_list_shapes.py (99%) rename tests/{run_agent => agent}/test_background_review_summary.py (100%) rename tests/{run_agent => agent}/test_background_review_toolset_restriction.py (100%) rename tests/{ => agent}/test_bitwarden_secrets.py (99%) rename tests/{run_agent => agent}/test_callable_api_key.py (100%) rename tests/agent/{test_cascading_interrupt_6600.py => test_cascading_interrupt.py} (100%) rename tests/{run_agent => agent}/test_codex_app_server_compaction.py (100%) rename tests/{run_agent => agent}/test_codex_app_server_integration.py (100%) rename tests/{run_agent => agent}/test_codex_app_server_lifecycle.py (100%) rename tests/{run_agent => agent}/test_codex_multimodal_tool_result.py (100%) rename tests/{run_agent => agent}/test_codex_no_tools_nonetype.py (100%) rename tests/{run_agent => agent}/test_codex_sdk_transform_bypass.py (100%) rename tests/{run_agent => agent}/test_codex_silent_hang_hint.py (100%) rename tests/{run_agent => agent}/test_codex_xai_oauth_recovery.py (100%) rename tests/{ => agent}/test_command_secret_source.py (99%) rename tests/{run_agent => agent}/test_commit_memory_session_context_engine.py (100%) rename tests/{ => agent}/test_compaction_prompt_rebuild.py (100%) rename tests/{run_agent => agent}/test_compress_context_fallback_shim.py (100%) rename tests/{run_agent => agent}/test_compress_focus_plugin_fallback.py (100%) rename tests/{run_agent => agent}/test_compression_abort_state_reset.py (100%) rename tests/{run_agent => agent}/test_compression_boundary.py (100%) rename tests/{run_agent => agent}/test_compression_boundary_hook.py (100%) rename tests/{run_agent => agent}/test_compression_budget_rearm.py (100%) rename tests/{run_agent => agent}/test_compression_budget_refund.py (100%) rename tests/{run_agent => agent}/test_compression_closed_adoption.py (100%) rename tests/agent/{test_compression_count_warning_36908.py => test_compression_count_warning.py} (100%) rename tests/{run_agent => agent}/test_compression_feasibility.py (100%) rename tests/{run_agent => agent}/test_compression_lock_defer.py (99%) rename tests/{run_agent => agent}/test_compression_persistence.py (100%) rename tests/agent/{test_compression_review_76354.py => test_compression_review.py} (100%) rename tests/agent/{test_compression_stall_fallback_78981.py => test_compression_stall_fallback.py} (100%) rename tests/agent/{test_compression_stall_interrupt_96775.py => test_compression_stall_interrupt.py} (100%) rename tests/{run_agent => agent}/test_compression_timeout_terminal.py (100%) rename tests/{run_agent => agent}/test_compression_trigger_excludes_reasoning.py (100%) rename tests/agent/{test_compression_worker_isolation_76354.py => test_compression_worker_isolation.py} (100%) rename tests/{run_agent => agent}/test_compressor_fallback_update.py (100%) rename tests/{run_agent => agent}/test_concurrent_interrupt.py (100%) rename tests/{run_agent/test_18028_content_policy_blocked.py => agent/test_content_policy_blocked.py} (100%) rename tests/{run_agent/test_1630_context_overflow_loop.py => agent/test_context_overflow_loop.py} (100%) rename tests/{run_agent => agent}/test_context_token_tracking.py (100%) rename tests/{run_agent => agent}/test_continuation_ceiling_wedge.py (100%) rename tests/{run_agent => agent}/test_continuation_repetition_guard.py (100%) rename tests/{run_agent => agent}/test_conversation_fallback_state.py (100%) rename tests/{ => agent}/test_copilot_initiator.py (100%) rename tests/{run_agent => agent}/test_copilot_native_vision_headers.py (100%) rename tests/{run_agent => agent}/test_corruption_recovery_guidance.py (100%) rename tests/{run_agent => agent}/test_create_openai_client_disables_sdk_retries.py (100%) rename tests/{run_agent => agent}/test_create_openai_client_kwargs_isolation.py (100%) rename tests/{run_agent => agent}/test_create_openai_client_proxy_env.py (100%) rename tests/{run_agent => agent}/test_create_openai_client_reuse.py (100%) rename tests/{run_agent => agent}/test_create_openai_client_ssl_verify.py (100%) rename tests/{run_agent/test_63425_credential_pool_auto_detect.py => agent/test_credential_pool_auto_detect.py} (100%) rename tests/{run_agent => agent}/test_credential_pool_interrupt.py (100%) rename tests/{run_agent => agent}/test_credential_rotation_route_settings.py (100%) rename tests/{run_agent => agent}/test_credits_notices_toggle.py (100%) rename tests/agent/{test_cron_inflight_prompt_reappend_100818.py => test_cron_inflight_prompt_reappend.py} (100%) rename tests/agent/{test_cron_inline_api_call_62151.py => test_cron_inline_api_call.py} (100%) rename tests/{run_agent => agent}/test_cross_process_turn_lease.py (100%) rename tests/{ => agent}/test_ctx_halving_fix.py (100%) rename tests/{run_agent => agent}/test_custom_provider_extra_headers_client.py (100%) rename tests/{run_agent => agent}/test_deepseek_reasoning_content_echo.py (100%) rename tests/{run_agent => agent}/test_deepseek_v4_thinking_live.py (98%) rename tests/{run_agent => agent}/test_dict_tool_call_args.py (100%) rename tests/{run_agent => agent}/test_direct_contexts_stream_inline.py (100%) rename tests/{run_agent => agent}/test_dropped_tool_call_recovery.py (100%) rename tests/{run_agent => agent}/test_empty_response_recovery_persistence.py (100%) rename tests/{run_agent => agent}/test_empty_terminal_reasoning_surface.py (100%) rename tests/{run_agent => agent}/test_entitlement_fail_closed.py (100%) rename tests/{run_agent => agent}/test_env_credential_turn_refresh.py (100%) rename tests/{ => agent}/test_env_loader_applied_homes.py (100%) rename tests/{ => agent}/test_env_loader_secret_sources.py (99%) rename tests/agent/{test_estimator_parity_84371.py => test_estimator_parity.py} (100%) rename tests/{run_agent => agent}/test_exit_cleanup_interrupt.py (100%) rename tests/{run_agent => agent}/test_export_current_turn_boundary.py (100%) rename tests/{run_agent/test_32646_fallback_429_after_timeout.py => agent/test_fallback_429_after_timeout.py} (100%) rename tests/{run_agent => agent}/test_fallback_api_mode_preservation.py (100%) rename tests/{run_agent => agent}/test_fallback_credential_isolation.py (100%) rename tests/{run_agent/test_24996_fallback_exhaustion_cooldown.py => agent/test_fallback_exhaustion_cooldown.py} (100%) rename tests/{run_agent => agent}/test_fallback_reasoning_override.py (100%) rename tests/{run_agent => agent}/test_file_mutation_verifier.py (100%) rename tests/{run_agent => agent}/test_fireworks_live.py (97%) rename tests/{run_agent => agent}/test_first_chunk_at_hook.py (100%) rename tests/{run_agent => agent}/test_flush_diverts_on_corrupt_state_db.py (100%) rename tests/{run_agent => agent}/test_gemini_native_reservation.py (100%) rename tests/{ => agent}/test_hermetic_side_effect_guards.py (100%) rename tests/{run_agent => agent}/test_identity_flush.py (100%) rename tests/{run_agent/test_69078_image_corrupt_recovery.py => agent/test_image_corrupt_recovery.py} (99%) rename tests/{run_agent => agent}/test_image_generate_parallel.py (100%) rename tests/{run_agent => agent}/test_image_rejection_fallback.py (100%) rename tests/{run_agent => agent}/test_image_shrink_recovery.py (100%) rename tests/{run_agent/test_in_place_commit_rollback_99477.py => agent/test_in_place_commit_rollback.py} (100%) rename tests/{run_agent => agent}/test_in_place_compaction.py (100%) rename tests/{run_agent/test_in_place_persist_marker_98450.py => agent/test_in_place_persist_marker.py} (100%) rename tests/{run_agent => agent}/test_infinite_compaction_loop.py (100%) rename tests/{run_agent => agent}/test_init_fallback_on_exhausted_pool.py (100%) rename tests/{run_agent => agent}/test_interactive_interrupt.py (100%) rename tests/{run_agent => agent}/test_interpreter_shutdown_turn_exit.py (100%) rename tests/{run_agent => agent}/test_interrupt_propagation.py (100%) rename tests/{run_agent => agent}/test_invalid_context_length_warning.py (100%) rename tests/{ => agent}/test_iron_proxy.py (99%) rename tests/{ => agent}/test_iron_proxy_e2e.py (100%) rename tests/{run_agent => agent}/test_iteration_budget_race.py (100%) rename tests/{run_agent => agent}/test_jsondecodeerror_retryable.py (100%) rename tests/{run_agent => agent}/test_last_reasoning_per_turn.py (100%) rename tests/{run_agent => agent}/test_length_continuation_thinking_exhaustion.py (100%) rename tests/{run_agent => agent}/test_lmstudio_load_mode.py (100%) rename tests/agent/{test_load_time_durability_stamp_92231.py => test_load_time_durability_stamp.py} (100%) rename tests/{run_agent => agent}/test_long_context_tier_429.py (100%) rename tests/{run_agent => agent}/test_malformed_tool_arguments.py (100%) rename tests/{run_agent => agent}/test_materialize_data_url_cleanup.py (100%) rename tests/{run_agent => agent}/test_memory_nudge_counter_hydration.py (100%) rename tests/{run_agent => agent}/test_memory_provider_init.py (100%) rename tests/{run_agent => agent}/test_memory_sync_interrupted.py (98%) rename tests/{ => agent}/test_message_reactions.py (100%) rename tests/{run_agent => agent}/test_message_sequence_repair.py (100%) rename tests/agent/{test_moa_cold_start_cache_66793.py => test_moa_cold_start_cache.py} (100%) rename tests/{run_agent => agent}/test_moa_fanout_cadence.py (100%) rename tests/{run_agent => agent}/test_moa_loop_mode.py (100%) rename tests/{test_moa_prepared_request_leak_78382.py => agent/test_moa_prepared_request_leak.py} (100%) rename tests/{run_agent => agent}/test_moa_privacy_filter.py (100%) rename tests/{run_agent => agent}/test_moa_streaming.py (100%) rename tests/{run_agent => agent}/test_model_streaming_config.py (100%) rename tests/{run_agent/test_66267_multimodal_interim.py => agent/test_multimodal_interim.py} (100%) rename tests/{run_agent => agent}/test_multimodal_tool_content_recovery.py (100%) rename tests/{run_agent => agent}/test_native_compaction.py (100%) rename tests/{run_agent => agent}/test_native_compaction_nontext_retention.py (100%) rename tests/{run_agent => agent}/test_native_compaction_summary_retention.py (100%) rename tests/{run_agent => agent}/test_native_compaction_switch_capabilities.py (100%) rename tests/{run_agent => agent}/test_nonretryable_error_html_summary.py (100%) rename tests/{run_agent => agent}/test_notice_spine.py (98%) rename tests/{run_agent => agent}/test_nous_429_fallback_reentry.py (100%) rename tests/{run_agent => agent}/test_nous_fallback_unavailable.py (100%) rename tests/{ => agent}/test_ollama_num_ctx.py (100%) rename tests/{ => agent}/test_onepassword_secrets.py (99%) rename tests/{run_agent => agent}/test_openai_client_lifecycle.py (100%) rename tests/{run_agent => agent}/test_opencode_free_client_headers.py (100%) rename tests/{run_agent/test_92450_outer_error_retry_bound.py => agent/test_outer_error_retry_bound.py} (100%) rename tests/{ => agent}/test_output_cap_parsing.py (100%) rename tests/{run_agent => agent}/test_overflow_overhead_aware_tokens.py (100%) rename tests/{run_agent/test_overflow_partial_terminal_106260.py => agent/test_overflow_partial_terminal.py} (100%) rename tests/{run_agent => agent}/test_partial_stream_finish_reason.py (100%) rename tests/{run_agent => agent}/test_per_model_compression_threshold.py (100%) rename tests/{run_agent => agent}/test_per_model_threshold_init_ordering.py (100%) rename tests/{run_agent => agent}/test_percentage_clamp.py (100%) rename tests/{run_agent => agent}/test_plugin_context_engine_init.py (100%) rename tests/{run_agent => agent}/test_plugin_stream_hooks.py (100%) rename tests/{run_agent => agent}/test_post_tool_compression_attempt_cap.py (100%) rename tests/{run_agent/test_pre_compress_memory_context.py => agent/test_pre_compress_memory_context_handoff.py} (100%) rename tests/{run_agent => agent}/test_preflight_compression_cap_e2e.py (100%) rename tests/{run_agent => agent}/test_preflight_timeout_typed_result.py (100%) rename tests/{run_agent => agent}/test_primary_runtime_restore.py (100%) rename tests/{ => agent}/test_private_credential_writers.py (100%) rename tests/{run_agent => agent}/test_proactive_prune_loop_wiring.py (100%) rename tests/agent/{test_protected_tail_pressure_61932.py => test_protected_tail_pressure.py} (100%) rename tests/{run_agent => agent}/test_provider_attribution_headers.py (100%) rename tests/{run_agent => agent}/test_provider_fallback.py (100%) rename tests/{run_agent => agent}/test_provider_parity.py (100%) rename tests/{run_agent => agent}/test_reasoning_echo_resolver_e2e.py (100%) rename tests/{ => agent}/test_redaction_registry.py (100%) rename tests/{run_agent => agent}/test_repair_reanchors_current_turn_user_idx.py (100%) rename tests/{run_agent => agent}/test_repair_tool_call_arguments.py (100%) rename tests/{run_agent => agent}/test_repair_tool_call_name.py (100%) rename tests/{run_agent => agent}/test_request_client_reuse_abort_races.py (100%) rename tests/{run_agent => agent}/test_reset_aware_primary_restore.py (100%) rename tests/{run_agent => agent}/test_retry_status_buffer.py (100%) rename tests/{ => agent}/test_retry_utils.py (100%) rename tests/{run_agent => agent}/test_review_prompt_class_first.py (100%) rename tests/agent/{test_rotation_flush_persisted_boundary_68196.py => test_rotation_flush_persisted_boundary.py} (100%) rename tests/{run_agent => agent}/test_run_agent.py (100%) rename tests/{run_agent => agent}/test_run_agent_codex_responses.py (100%) rename tests/{run_agent => agent}/test_run_agent_multimodal_prologue.py (100%) rename tests/{run_agent => agent}/test_sanitiser_escalation.py (100%) rename tests/{run_agent => agent}/test_sequential_chats_live.py (98%) rename tests/{run_agent => agent}/test_sequential_tool_timeout.py (100%) rename tests/{run_agent => agent}/test_session_activity_persist.py (100%) rename tests/{run_agent => agent}/test_session_id_env.py (100%) rename tests/{run_agent => agent}/test_session_meta_filtering.py (100%) rename tests/{run_agent => agent}/test_session_reset_fix.py (100%) rename tests/agent/{test_session_rotation_flush_cold_resume_68454.py => test_session_rotation_flush_cold_resume.py} (100%) rename tests/{run_agent => agent}/test_session_source.py (100%) rename tests/{run_agent/test_70773_shared_client_fd_corruption.py => agent/test_shared_client_fd_corruption.py} (100%) rename tests/{run_agent => agent}/test_skill_applied_notifications.py (100%) rename tests/agent/{test_skip_memory_store_65429.py => test_skip_memory_store.py} (100%) rename tests/{ => agent}/test_sql_injection.py (100%) rename tests/{run_agent => agent}/test_start_order_gate.py (100%) rename tests/{run_agent => agent}/test_steer.py (100%) rename tests/{run_agent => agent}/test_stream_drop_logging.py (100%) rename tests/{run_agent => agent}/test_stream_interrupt_retry.py (100%) rename tests/{run_agent/test_stream_single_writer_65991.py => agent/test_stream_single_writer.py} (100%) rename tests/{run_agent => agent}/test_stream_stale_breaker_reset.py (98%) rename tests/{run_agent => agent}/test_stream_stale_circuit_breaker.py (98%) rename tests/{run_agent => agent}/test_streamed_text_accumulation.py (100%) rename tests/{run_agent => agent}/test_streaming.py (100%) rename tests/{run_agent => agent}/test_streaming_tool_call_repair.py (100%) rename tests/{run_agent => agent}/test_strict_api_validation.py (100%) rename tests/{run_agent => agent}/test_strip_reasoning_tags_cli.py (100%) rename tests/{run_agent => agent}/test_summarize_api_error.py (100%) rename tests/{run_agent => agent}/test_switch_model_bedrock_sigv4.py (100%) rename tests/{run_agent => agent}/test_switch_model_context.py (100%) rename tests/{run_agent => agent}/test_switch_model_fallback_prune.py (100%) rename tests/{run_agent/test_switch_model_pool_reload_52727.py => agent/test_switch_model_pool_reload.py} (100%) rename tests/{run_agent => agent}/test_switch_model_reapplies_headers.py (100%) rename tests/{run_agent => agent}/test_switch_model_reasoning_override.py (100%) rename tests/{run_agent => agent}/test_switch_model_rollback.py (100%) rename tests/{run_agent => agent}/test_switch_model_stale_base_url.py (100%) rename tests/{run_agent/test_81641_text_turn_incremental_persistence.py => agent/test_text_turn_incremental_persistence.py} (100%) rename tests/{run_agent => agent}/test_thinking_only_sanitizer.py (100%) rename tests/{run_agent => agent}/test_thinking_prefill_trailing_turn.py (100%) rename tests/{run_agent => agent}/test_thinking_sig_recovery_persistence.py (100%) rename tests/{run_agent => agent}/test_tls_fd_recycle_corruption.py (100%) rename tests/{run_agent => agent}/test_token_persistence_non_cli.py (100%) rename tests/{run_agent => agent}/test_tool_activity_heartbeat.py (100%) rename tests/{run_agent => agent}/test_tool_arg_coercion.py (100%) rename tests/{run_agent => agent}/test_tool_batch_segmentation.py (100%) rename tests/{run_agent => agent}/test_tool_call_args_sanitizer.py (100%) rename tests/{run_agent => agent}/test_tool_call_guardrail_runtime.py (100%) rename tests/{run_agent => agent}/test_tool_call_incremental_persistence.py (100%) rename tests/{run_agent => agent}/test_tool_executor_contextvar_propagation.py (100%) rename tests/{run_agent => agent}/test_tool_name_db_persistence.py (100%) rename tests/{run_agent => agent}/test_turn_completion_explainer.py (99%) rename tests/{run_agent => agent}/test_turn_liveness_watchdog.py (100%) rename tests/{run_agent => agent}/test_unicode_ascii_codec.py (100%) rename tests/{run_agent => agent}/test_verification_continuation_budget.py (100%) rename tests/{run_agent => agent}/test_vision_aware_preprocessing.py (100%) rename tests/agent/{test_vision_routing_31179.py => test_vision_routing.py} (100%) rename tests/{run_agent => agent}/test_vision_tool_messages.py (100%) rename tests/{run_agent => agent}/test_wait_state_visibility.py (100%) delete mode 100644 tests/cli/__init__.py delete mode 100644 tests/cli/conftest.py delete mode 100644 tests/cli/test_cli_skin_integration.py rename tests/cron/{test_cron_direct_api_call_62151.py => test_cron_direct_api_call.py} (100%) rename tests/cron/{test_cron_emfile_stall_87644.py => test_cron_emfile_stall.py} (100%) rename tests/{ => cron}/test_cron_manage_profile_scope.py (100%) rename tests/cron/{test_cron_run_stale_claim_reap_86721.py => test_cron_run_stale_claim_reap.py} (100%) rename tests/cron/{test_87033_cronjob_gateway_liveness.py => test_cronjob_gateway_liveness.py} (100%) rename tests/{ => cron}/test_estop.py (100%) rename tests/cron/{test_jobs_shrink_merge_80624.py => test_jobs_shrink_merge.py} (100%) rename tests/cron/{test_ticker_stall_60703.py => test_ticker_stall.py} (100%) rename tests/{ => cron}/test_timezone.py (100%) rename tests/{ => gateway}/relay/test_relay_format_hints.py (100%) rename tests/{ => gateway}/relay/test_relay_inchannel_continuable.py (100%) rename tests/{ => gateway}/relay/test_relay_prompt_ack_stream_isolation.py (100%) rename tests/gateway/{test_35809_auto_reset_clean_context.py => test_auto_reset_clean_context.py} (100%) rename tests/gateway/{test_10710_auto_reset_evicts_cached_agent.py => test_auto_reset_evicts_cached_agent.py} (100%) rename tests/gateway/{test_53175_cleanup_off_loop.py => test_cleanup_off_loop.py} (100%) rename tests/gateway/{test_compression_interrupt_demotion_56391.py => test_compression_interrupt_demotion.py} (100%) rename tests/gateway/{test_42039_duplicate_user_message.py => test_duplicate_user_message.py} (100%) rename tests/{ => gateway}/test_empty_model_fallback.py (100%) rename tests/gateway/{test_env_override_explicit_disable_48820.py => test_env_override_explicit_disable.py} (100%) rename tests/gateway/{test_71671_faulthandler_no_stderr.py => test_faulthandler_no_stderr.py} (100%) rename tests/{ => gateway}/test_gateway_streaming_nested_config.py (100%) rename tests/gateway/{test_internal_notification_marker_82888.py => test_internal_notification_marker.py} (100%) rename tests/gateway/{test_73771_media_resend_dedup.py => test_media_resend_dedup.py} (100%) rename tests/gateway/{test_73297_memory_flush_on_reset.py => test_memory_flush_on_reset.py} (100%) rename tests/gateway/{test_48031_model_switch_after_auto_reset.py => test_model_switch_after_auto_reset.py} (100%) rename tests/gateway/{test_64674_multiplex_primary_token_scope.py => test_multiplex_primary_token_scope.py} (100%) rename tests/gateway/{test_priority_path_compression_demotion_56391.py => test_priority_path_compression_demotion.py} (100%) rename tests/{ => gateway}/test_profile_isolation_runtime.py (100%) rename tests/{test_89315_replace_ownership_guard.py => gateway/test_replace_ownership_guard.py} (100%) rename tests/gateway/{test_35994_reset_button_deadlock.py => test_reset_button_deadlock.py} (100%) rename tests/gateway/{test_session_continuity_82616.py => test_session_continuity.py} (100%) rename tests/gateway/{test_session_split_brain_11016.py => test_session_split_brain.py} (100%) rename tests/gateway/{test_13121_shutdown_inflight_transcript_flush.py => test_shutdown_inflight_transcript_flush.py} (100%) rename tests/gateway/{test_silent_partial_delivery_95382.py => test_silent_partial_delivery.py} (100%) rename tests/{ => gateway}/test_slack_thread_require_mention.py (100%) rename tests/gateway/{test_25107_stale_base_url_api_mode.py => test_stale_base_url_api_mode.py} (100%) rename tests/gateway/{test_subagent_protection_30170.py => test_subagent_protection.py} (100%) rename tests/gateway/{test_telegram_closewait_limits_31599.py => test_telegram_closewait_limits.py} (100%) rename tests/gateway/{test_telegram_fallback_pool_release_71593.py => test_telegram_fallback_pool_release.py} (100%) rename tests/gateway/{test_telegram_prune_stale_topic_binding_31501.py => test_telegram_prune_stale_topic_binding.py} (100%) rename tests/gateway/{test_telegram_topic_profile_isolation_76423.py => test_telegram_topic_profile_isolation.py} (100%) rename tests/gateway/{test_telegram_topic_profile_routing_76423.py => test_telegram_topic_profile_routing.py} (100%) rename tests/{ => gateway}/test_tini_shim.py (98%) rename tests/gateway/{test_transcript_read_failure_100788.py => test_transcript_read_failure.py} (100%) rename tests/gateway/{test_7100_transient_failure_transcript.py => test_transient_failure_transcript.py} (100%) rename tests/gateway/{test_watchdog_review_76354.py => test_watchdog_review.py} (100%) rename tests/gateway/{test_75349_whatsapp_multiplex_secret_scope.py => test_whatsapp_multiplex_secret_scope.py} (100%) rename tests/{ => gateway}/test_yuanbao_integration.py (99%) rename tests/{ => gateway}/test_yuanbao_markdown.py (96%) rename tests/{ => gateway}/test_yuanbao_pipeline.py (99%) rename tests/{ => gateway}/test_yuanbao_proto.py (99%) rename tests/{ => gateway}/test_yuanbao_reconnect_set_active.py (97%) rename tests/{ => gateway}/test_yuanbao_shutdown.py (97%) rename tests/{ => hermes_cli}/test_active_session_exclusivity.py (100%) rename tests/{ => hermes_cli}/test_audio_playback_guard.py (98%) rename tests/{cli => hermes_cli}/test_bang_shell_mode.py (100%) rename tests/{cli => hermes_cli}/test_bracketed_paste_timeout.py (100%) rename tests/{cli => hermes_cli}/test_branch_command.py (100%) rename tests/{cli => hermes_cli}/test_busy_input_mode_command.py (100%) rename tests/{cli => hermes_cli}/test_chat_q_exit_clear.py (100%) rename tests/{cli => hermes_cli}/test_cli_active_agent_ref_wiring.py (100%) rename tests/{cli => hermes_cli}/test_cli_approval_ui.py (100%) rename tests/{cli => hermes_cli}/test_cli_async_delegation_delivery.py (100%) rename tests/{cli => hermes_cli}/test_cli_background_busy_path.py (98%) rename tests/{cli => hermes_cli}/test_cli_background_status_indicator.py (100%) rename tests/{cli => hermes_cli}/test_cli_background_tui_refresh.py (100%) rename tests/{cli => hermes_cli}/test_cli_bracketed_paste_sanitizer.py (100%) rename tests/{cli => hermes_cli}/test_cli_browser_connect.py (100%) rename tests/{cli => hermes_cli}/test_cli_clarify_batch.py (99%) rename tests/{cli => hermes_cli}/test_cli_cmd_backspace.py (100%) rename tests/{cli => hermes_cli}/test_cli_codex_context_reference.py (100%) rename tests/{cli => hermes_cli}/test_cli_context_warning.py (100%) rename tests/{cli => hermes_cli}/test_cli_copy_command.py (100%) rename tests/{cli => hermes_cli}/test_cli_delegate_background_notice.py (100%) rename tests/{cli => hermes_cli}/test_cli_extension_hooks.py (100%) rename tests/{cli => hermes_cli}/test_cli_external_editor.py (100%) rename tests/{cli => hermes_cli}/test_cli_file_drop.py (100%) rename tests/{cli => hermes_cli}/test_cli_first_run_setup.py (100%) rename tests/{cli => hermes_cli}/test_cli_force_redraw.py (100%) rename tests/{cli => hermes_cli}/test_cli_goal_interrupt.py (100%) rename tests/{cli => hermes_cli}/test_cli_goal_kick_prompt.py (100%) rename tests/{cli => hermes_cli}/test_cli_goal_parked_resume.py (100%) rename tests/{cli => hermes_cli}/test_cli_image_command.py (100%) rename tests/{cli => hermes_cli}/test_cli_init.py (99%) rename tests/{cli => hermes_cli}/test_cli_insights_command.py (100%) rename tests/{cli => hermes_cli}/test_cli_interrupt_ack_race.py (100%) rename tests/{cli => hermes_cli}/test_cli_interrupt_drain_regression.py (100%) rename tests/{cli => hermes_cli}/test_cli_interrupt_subagent.py (100%) rename tests/{cli => hermes_cli}/test_cli_light_mode.py (100%) rename tests/{cli => hermes_cli}/test_cli_loading_indicator.py (100%) rename tests/{ => hermes_cli}/test_cli_manual_compress.py (100%) rename tests/{cli => hermes_cli}/test_cli_markdown_rendering.py (100%) rename tests/{cli => hermes_cli}/test_cli_mcp_config_watch.py (100%) rename tests/{cli => hermes_cli}/test_cli_new_session.py (99%) rename tests/hermes_cli/{test_cli_partial_update_hint_96900.py => test_cli_partial_update_hint.py} (100%) rename tests/{cli => hermes_cli}/test_cli_pet_pane.py (100%) rename tests/{cli => hermes_cli}/test_cli_prefix_matching.py (100%) rename tests/{cli => hermes_cli}/test_cli_preloaded_skills.py (100%) rename tests/{cli => hermes_cli}/test_cli_provider_resolution.py (100%) rename tests/{cli => hermes_cli}/test_cli_queue_paste.py (100%) rename tests/{ => hermes_cli}/test_cli_quiet_stdout_leak.py (100%) rename tests/{cli => hermes_cli}/test_cli_reload_skills.py (100%) rename tests/{cli => hermes_cli}/test_cli_resume_command.py (100%) rename tests/{cli => hermes_cli}/test_cli_retry.py (100%) rename tests/{cli => hermes_cli}/test_cli_save_config_value.py (100%) rename tests/{cli => hermes_cli}/test_cli_secret_capture.py (100%) rename tests/{ => hermes_cli}/test_cli_session_store_shared_registry.py (100%) rename tests/{cli => hermes_cli}/test_cli_shift_enter_newline.py (100%) rename tests/{cli => hermes_cli}/test_cli_shutdown_memory_messages.py (100%) rename tests/{ => hermes_cli}/test_cli_skin_integration.py (86%) rename tests/{cli => hermes_cli}/test_cli_status_bar.py (100%) rename tests/{cli => hermes_cli}/test_cli_status_bar_goal.py (100%) rename tests/{cli => hermes_cli}/test_cli_status_command.py (100%) rename tests/{cli => hermes_cli}/test_cli_steer_busy_path.py (100%) rename tests/{cli => hermes_cli}/test_cli_terminal_response_sanitizer.py (100%) rename tests/{cli => hermes_cli}/test_cli_terminal_shortcuts.py (100%) rename tests/{cli => hermes_cli}/test_cli_tools_command.py (100%) rename tests/{cli => hermes_cli}/test_cli_user_message_preview.py (100%) rename tests/{cli => hermes_cli}/test_cli_yolo_resume_persistence.py (100%) rename tests/{cli => hermes_cli}/test_cli_yolo_toggle.py (100%) rename tests/{ => hermes_cli}/test_code_skew.py (100%) rename tests/{cli => hermes_cli}/test_command_palette.py (100%) rename tests/{ => hermes_cli}/test_compat_manifest_targets.py (98%) rename tests/{cli => hermes_cli}/test_completion_backlog.py (100%) rename tests/{cli => hermes_cli}/test_compress_flags.py (100%) rename tests/{cli => hermes_cli}/test_compress_focus.py (100%) rename tests/{cli => hermes_cli}/test_compress_here.py (100%) rename tests/{cli => hermes_cli}/test_compress_type_ahead.py (100%) rename tests/{cli => hermes_cli}/test_cpr_local_leak.py (100%) rename tests/{cli => hermes_cli}/test_cprint_bg_thread.py (100%) rename tests/{cli => hermes_cli}/test_ctrl_enter_newline.py (100%) rename tests/{cli => hermes_cli}/test_cwd_env_respect.py (100%) rename tests/{cli => hermes_cli}/test_destructive_slash_confirm.py (100%) rename tests/{cli => hermes_cli}/test_destructive_slash_inline_skip_e2e.py (100%) rename tests/{ => hermes_cli}/test_empty_session_hygiene.py (100%) rename tests/hermes_cli/{test_ensure_hermes_home_uid_34107.py => test_ensure_hermes_home_uid.py} (100%) rename tests/{ => hermes_cli}/test_env_loader_op_bootstrap.py (99%) rename tests/{cli => hermes_cli}/test_exit_delete_session.py (100%) rename tests/{cli => hermes_cli}/test_exit_summary_resume_hint.py (100%) rename tests/{cli => hermes_cli}/test_exit_watchdog_signal_arm.py (100%) rename tests/{cli => hermes_cli}/test_fast_command.py (100%) rename tests/{cli => hermes_cli}/test_finite_chat_delegation.py (100%) rename tests/{cli => hermes_cli}/test_focus_view.py (100%) rename tests/{ => hermes_cli}/test_gitlock.py (100%) rename tests/hermes_cli/{test_25106_global_switch_persists_base_url_api_mode.py => test_global_switch_persists_base_url_api_mode.py} (100%) rename tests/{cli => hermes_cli}/test_handoff_cleanup_race.py (100%) rename tests/{cli => hermes_cli}/test_handoff_slow_dispatch_timeout.py (100%) rename tests/{cli => hermes_cli}/test_indicator_command.py (100%) rename tests/{cli => hermes_cli}/test_interrupt_output_history_regression.py (100%) rename tests/{ => hermes_cli}/test_iron_proxy_cli.py (100%) rename tests/{cli => hermes_cli}/test_kitty_keypad_parity.py (100%) rename tests/{ => hermes_cli}/test_lazy_secrets_dispatch.py (98%) rename tests/{ => hermes_cli}/test_lazy_secrets_import.py (97%) rename tests/{ => hermes_cli}/test_list_recent_user_messages_handoffs.py (100%) rename tests/{cli => hermes_cli}/test_manual_compress.py (100%) rename tests/{ => hermes_cli}/test_minimax_model_validation.py (100%) rename tests/{ => hermes_cli}/test_minimax_oauth.py (100%) rename tests/{cli => hermes_cli}/test_moa_command.py (100%) rename tests/hermes_cli/{test_model_alias_credentials_83612.py => test_model_alias_credentials.py} (100%) rename tests/{cli => hermes_cli}/test_model_picker_filter.py (100%) rename tests/hermes_cli/{test_model_prefix_routing_87189.py => test_model_prefix_routing.py} (100%) rename tests/{cli => hermes_cli}/test_modify_other_keys_aliases.py (100%) rename tests/{cli => hermes_cli}/test_oneshot_resumed_session_persist.py (100%) rename tests/{cli => hermes_cli}/test_partial_compress.py (100%) rename tests/{cli => hermes_cli}/test_personality_none.py (100%) rename tests/{ => hermes_cli}/test_plugin_compat_notice.py (98%) rename tests/{cli => hermes_cli}/test_prefill_config.py (100%) rename tests/{cli => hermes_cli}/test_prepend_note_to_message.py (100%) rename tests/{cli => hermes_cli}/test_prompt_stash.py (100%) rename tests/{cli => hermes_cli}/test_prompt_stash_cli.py (98%) rename tests/{cli => hermes_cli}/test_prompt_text_input_thread_safety.py (100%) rename tests/{ => hermes_cli}/test_pty_keepalive_ws.py (100%) rename tests/{ => hermes_cli}/test_pty_session.py (100%) rename tests/{cli => hermes_cli}/test_quick_commands.py (100%) rename tests/{cli => hermes_cli}/test_quiet_turn_author.py (100%) rename tests/{cli => hermes_cli}/test_reasoning_command.py (100%) rename tests/hermes_cli/{test_regression_16767.py => test_regression.py} (98%) rename tests/{ => hermes_cli}/test_resource_limits.py (100%) rename tests/{cli => hermes_cli}/test_resume_display.py (100%) rename tests/{cli => hermes_cli}/test_resume_model_restore.py (100%) rename tests/{cli => hermes_cli}/test_resume_quiet_stderr.py (100%) rename tests/{cli => hermes_cli}/test_save_conversation_location.py (100%) rename tests/{cli => hermes_cli}/test_session_boundary_hooks.py (100%) rename tests/{ => hermes_cli}/test_session_detail_malformed_db.py (100%) rename tests/{ => hermes_cli}/test_session_export_batch.py (100%) rename tests/{ => hermes_cli}/test_session_search_context_batch.py (100%) rename tests/{ => hermes_cli}/test_session_skill_previews.py (100%) rename tests/{ => hermes_cli}/test_session_system_prompt_dedup.py (100%) rename tests/{ => hermes_cli}/test_session_vacuum_config.py (98%) rename tests/{cli => hermes_cli}/test_show_config_credential.py (100%) rename tests/{cli => hermes_cli}/test_single_query_clarify.py (100%) rename tests/{cli => hermes_cli}/test_single_query_session_finalize.py (100%) rename tests/{cli => hermes_cli}/test_slash_command_interrupt.py (100%) rename tests/{cli => hermes_cli}/test_slash_confirm_windows.py (100%) rename tests/{cli => hermes_cli}/test_slash_dispatch_table.py (100%) rename tests/{cli => hermes_cli}/test_slash_undo_title_robustness.py (100%) rename tests/hermes_cli/{test_startup_model_routing_87189.py => test_startup_model_routing.py} (100%) rename tests/{cli => hermes_cli}/test_status_bar_git_branch.py (100%) rename tests/{cli => hermes_cli}/test_status_lines_held_during_stream_box.py (100%) rename tests/{cli/test_steer_inline_repaint_34569.py => hermes_cli/test_steer_inline_repaint.py} (100%) rename tests/{cli => hermes_cli}/test_stream_delta_think_tag.py (100%) rename tests/{cli => hermes_cli}/test_stream_flush_left.py (100%) rename tests/{cli => hermes_cli}/test_stream_partial_line_flush.py (100%) rename tests/{cli => hermes_cli}/test_subagent_dock_collapse.py (100%) rename tests/{cli => hermes_cli}/test_subagent_dock_surface.py (100%) rename tests/{cli => hermes_cli}/test_subagent_monitor.py (100%) rename tests/{cli => hermes_cli}/test_subagent_monitor_prompts.py (100%) rename tests/{cli => hermes_cli}/test_subagent_monitor_resize.py (100%) rename tests/{cli => hermes_cli}/test_subagent_monitor_ui.py (100%) rename tests/{cli => hermes_cli}/test_subagent_notification_display.py (100%) rename tests/{cli => hermes_cli}/test_subagent_shortcut.py (100%) rename tests/{cli => hermes_cli}/test_terminal_interrupt_recovery.py (100%) rename tests/hermes_cli/{test_terminal_io_broken_81521.py => test_terminal_io_broken.py} (100%) rename tests/{cli => hermes_cli}/test_termios_drift_heal.py (100%) rename tests/{cli => hermes_cli}/test_tool_gen_start_dedupe.py (100%) rename tests/{cli => hermes_cli}/test_tool_progress_scrollback.py (100%) rename tests/{cli => hermes_cli}/test_transformed_stream_output.py (100%) rename tests/{cli => hermes_cli}/test_tui_rapid_enter_paste.py (100%) rename tests/{cli => hermes_cli}/test_tui_terminal_reset_on_exit.py (100%) rename tests/{cli => hermes_cli}/test_update_command.py (100%) rename tests/{cli => hermes_cli}/test_version_command.py (100%) rename tests/{cli => hermes_cli}/test_vim_mode_command.py (100%) rename tests/{ => hermes_cli}/test_web_server_sessiondb_eventloop.py (100%) rename tests/{ => hermes_cli}/test_web_server_status_topology_cache.py (100%) rename tests/{test_web_server.py => hermes_cli/test_web_server_ws_ping.py} (100%) rename tests/hermes_cli/{test_windows_gateway_job_teardown_48820.py => test_windows_gateway_job_teardown.py} (100%) rename tests/{cli => hermes_cli}/test_worktree.py (100%) rename tests/{cli => hermes_cli}/test_worktree_pushed_tier.py (100%) rename tests/{cli => hermes_cli}/test_worktree_security.py (100%) rename tests/{cli => hermes_cli}/test_worktree_selfheal.py (100%) rename tests/{cli => hermes_cli}/test_worktree_sync_base.py (100%) rename tests/{state/test_append_message_after_close_94736.py => hermes_state/test_append_message_after_close.py} (100%) rename tests/hermes_state/{test_88197_automatic_ended_stamp.py => test_automatic_ended_stamp.py} (100%) rename tests/{ => hermes_state}/test_background_review_session_isolation.py (100%) rename tests/{state => hermes_state}/test_compression_lease_refresh_before_publish.py (100%) rename tests/{state => hermes_state}/test_compression_lineage_guard.py (100%) rename tests/{ => hermes_state}/test_compression_watermark_commit.py (100%) rename tests/{ => hermes_state}/test_conftest_wal_gate.py (100%) rename tests/{state => hermes_state}/test_dedupe_migration_contention.py (100%) rename tests/{test_delegate_cascade_49148.py => hermes_state/test_delegate_cascade.py} (100%) rename tests/{state => hermes_state}/test_disk_full_error.py (100%) rename tests/hermes_state/{test_empty_sweep_archived_transcript_95868.py => test_empty_sweep_archived_transcript.py} (100%) rename tests/{ => hermes_state}/test_fts_cjk_bigram.py (99%) rename tests/{state => hermes_state}/test_fts_fresh_bootstrap_admission.py (100%) rename tests/{state => hermes_state}/test_fts_holder_instance_scope.py (100%) rename tests/{state => hermes_state}/test_fts_index_fail_open.py (100%) rename tests/{state => hermes_state}/test_fts_orphan_shadow_repair.py (100%) rename tests/{state => hermes_state}/test_fts_rebuild_admission.py (100%) rename tests/{state => hermes_state}/test_fts_runtime_rebuild.py (100%) rename tests/{ => hermes_state}/test_fts_tool_write_bounds.py (100%) rename tests/{state => hermes_state}/test_fts_trigram_cron_exclusion.py (100%) rename tests/{state => hermes_state}/test_fts_trigram_subagent_exclusion.py (100%) rename tests/{ => hermes_state}/test_fts_update_of_narrowing.py (100%) rename tests/{ => hermes_state}/test_guest_durability_barriers.py (100%) rename tests/{ => hermes_state}/test_hermes_state.py (100%) rename tests/{ => hermes_state}/test_hermes_state_compression_busy_retry.py (100%) rename tests/{ => hermes_state}/test_hermes_state_compression_locks.py (100%) rename tests/{ => hermes_state}/test_hermes_state_conn_lock_audit.py (98%) rename tests/{ => hermes_state}/test_hermes_state_ids.py (100%) rename tests/{ => hermes_state}/test_hermes_state_readonly_preflight.py (100%) rename tests/{ => hermes_state}/test_hermes_state_wal_fallback.py (100%) rename tests/{ => hermes_state}/test_journal_mode_config.py (100%) rename tests/{ => hermes_state}/test_journal_mode_upgrade_warning.py (100%) rename tests/{state => hermes_state}/test_no_locked_readers_gate.py (100%) rename tests/{state => hermes_state}/test_no_more_rows_retry.py (100%) rename tests/hermes_state/{test_94895_orphan_sweep_cross_backend.py => test_orphan_sweep_cross_backend.py} (100%) rename tests/{test_98924_readonly_fts_decode_error.py => hermes_state/test_readonly_fts_decode_error.py} (100%) rename tests/{ => hermes_state}/test_schema_read_probe.py (100%) rename tests/{ => hermes_state}/test_search_slow_query_log.py (100%) rename tests/{ => hermes_state}/test_session_db_context_manager.py (100%) rename tests/{ => hermes_state}/test_session_db_profile_stamp.py (100%) rename tests/{ => hermes_state}/test_session_db_read_conn_pool.py (100%) rename tests/{ => hermes_state}/test_session_db_read_path_split.py (100%) rename tests/{ => hermes_state}/test_session_delete_profile_isolation.py (100%) rename tests/{state => hermes_state}/test_session_git_metadata_generation.py (100%) rename tests/{state => hermes_state}/test_session_model_usage_pk_heal.py (100%) rename tests/{state => hermes_state}/test_session_turn_lease.py (100%) rename tests/{ => hermes_state}/test_session_workspace_binding.py (100%) rename tests/{ => hermes_state}/test_sqlite_lock_safe_inspection.py (100%) rename tests/{ => hermes_state}/test_sqlite_wal_reset_gate.py (100%) rename tests/{ => hermes_state}/test_stale_tool_call_marker_session_repair.py (100%) rename tests/{ => hermes_state}/test_state_db_fts_segment_collision_probe.py (100%) rename tests/{state => hermes_state}/test_state_db_holders.py (100%) rename tests/{state => hermes_state}/test_state_db_lock_fail_closed.py (100%) rename tests/{ => hermes_state}/test_state_db_malformed_repair.py (100%) rename tests/{ => hermes_state}/test_state_db_notadb_fail_closed.py (100%) rename tests/{ => hermes_state}/test_state_db_repair_live_writer_guard.py (100%) rename tests/{ => hermes_state}/test_state_db_repair_loop_cap.py (100%) rename tests/{ => hermes_state}/test_state_db_repair_loop_mtime.py (100%) rename tests/{ => hermes_state}/test_state_db_repair_non_destructive.py (100%) rename tests/{ => hermes_state}/test_state_db_stats.py (100%) rename tests/{state => hermes_state}/test_state_db_wal_unlink_race.py (100%) rename tests/{ => hermes_state}/test_state_db_write_durability.py (100%) rename tests/{ => hermes_state}/test_state_synchronous_pragma.py (98%) rename tests/hermes_state/{test_86366_tail_rewind_semantics.py => test_tail_rewind_semantics.py} (100%) rename tests/{ => hermes_state}/test_wal_checkpoint_strategy.py (100%) rename tests/hermes_state/{test_86366_watermark_interplay.py => test_watermark_interplay.py} (100%) rename tests/{state => hermes_state}/test_write_lock_patience.py (100%) rename tests/{state => hermes_state}/test_writer_conn_thread_safety.py (100%) rename tests/{ => hermes_state}/test_zeroed_state_db.py (100%) rename tests/{ => plugins}/test_honcho_client_concurrency.py (100%) rename tests/{ => plugins}/test_honcho_client_config.py (100%) rename tests/{ => plugins}/test_honcho_session_context.py (100%) rename tests/{ => plugins}/test_honcho_startup_fail_open.py (100%) rename tests/{ => plugins}/test_plugin_storage.py (100%) rename tests/{ => plugins}/test_plugin_utils.py (100%) rename tests/{ => plugins}/test_plugins_manage_profile_scope.py (100%) rename tests/{ => plugins}/test_secret_scope_plugin_families.py (100%) rename tests/{ => plugins}/test_telegram_polling_progress_ptb.py (100%) rename tests/{ => plugins}/test_transform_api_error_classification_hook.py (100%) rename tests/{ => plugins}/test_transform_llm_output_hook.py (100%) rename tests/{ => plugins}/test_transform_tool_result_hook.py (100%) delete mode 100644 tests/run_agent/__init__.py rename tests/{ => scripts/desktop_update}/test_desktop_update_linux_gate.py (95%) rename tests/{ => scripts/desktop_update}/test_desktop_update_shim_profile_cleanup.py (97%) rename tests/{ => scripts/desktop_update}/test_desktop_update_shim_progress.py (99%) rename tests/{ => scripts/desktop_update}/test_desktop_update_tcc_heal.py (99%) rename tests/{ => scripts/desktop_update}/test_desktop_update_windows_cwd.py (96%) rename tests/{ => scripts/desktop_update}/test_desktop_update_windows_pipe_drain.py (99%) rename tests/{ => scripts/desktop_update}/test_desktop_update_windows_progress.py (98%) rename tests/{ => scripts/desktop_update}/test_desktop_update_windows_python_handoff.py (98%) rename tests/{ => scripts/desktop_update}/test_desktop_update_windows_retry_policy.py (96%) rename tests/{ => scripts/desktop_update}/test_desktop_update_windows_timestamp.py (95%) rename tests/{ => scripts/desktop_update}/test_desktop_update_windows_ui_delivery.py (98%) rename tests/{ => scripts/install}/test_install_autostash_conflict_recovery.py (99%) rename tests/{ => scripts/install}/test_install_clone_throttle_fallback.py (98%) rename tests/{ => scripts/install}/test_install_commit_pin_rollback.py (98%) rename tests/{ => scripts/install}/test_install_diverged_update.py (97%) rename tests/{ => scripts/install}/test_install_lockfile_churn.py (98%) rename tests/{ => scripts/install}/test_install_macos_launcher.py (97%) rename tests/{ => scripts/install}/test_install_no_initial_commit.py (98%) rename tests/{ => scripts/install}/test_install_ps1_ascii_only.py (97%) rename tests/{ => scripts/install}/test_install_ps1_browser_install.py (93%) rename tests/{ => scripts/install}/test_install_ps1_managed_node_swap.py (98%) rename tests/{ => scripts/install}/test_install_ps1_managed_python_provenance.py (99%) rename tests/{ => scripts/install}/test_install_ps1_native_stderr_eap.py (97%) rename tests/{ => scripts/install}/test_install_ps1_node_path_for_npm.py (95%) rename tests/{ => scripts/install}/test_install_ps1_python_fallback_venv.py (98%) rename tests/{ => scripts/install}/test_install_ps1_resolver_strictmode.py (98%) rename tests/{ => scripts/install}/test_install_ps1_uv_install_fallback.py (98%) rename tests/{ => scripts/install}/test_install_ps1_uv_powershell_host.py (98%) rename tests/{ => scripts/install}/test_install_ps1_venv_process_tree.py (98%) rename tests/{ => scripts/install}/test_install_ps1_venv_recreate_safety.py (97%) rename tests/{ => scripts/install}/test_install_ps1_venv_rename_abort.py (97%) rename tests/{ => scripts/install}/test_install_ps1_venv_transaction_boundary.py (96%) rename tests/{ => scripts/install}/test_install_ps1_web_server_syntax_probe.py (96%) rename tests/{ => scripts/install}/test_install_scripts_computer_use.py (97%) rename tests/{ => scripts/install}/test_install_sh_acp_launcher.py (98%) rename tests/{ => scripts/install}/test_install_sh_bootstrap_marker.py (98%) rename tests/{ => scripts/install}/test_install_sh_browser_install.py (99%) rename tests/{ => scripts/install}/test_install_sh_install_method_stamp.py (96%) rename tests/{ => scripts/install}/test_install_sh_node_deps_failure.py (98%) rename tests/{ => scripts/install}/test_install_sh_node_deps_workspaces.py (98%) rename tests/{ => scripts/install}/test_install_sh_node_global_prefix.py (97%) rename tests/{ => scripts/install}/test_install_sh_node_npm_check.py (96%) rename tests/{ => scripts/install}/test_install_sh_node_prerelease.py (98%) rename tests/{test_install_sh_node_probe_87460.py => scripts/install/test_install_sh_node_probe.py} (99%) rename tests/{ => scripts/install}/test_install_sh_node_tarball_without_xz.py (97%) rename tests/{ => scripts/install}/test_install_sh_pythonpath_sanitization.py (94%) rename tests/{ => scripts/install}/test_install_sh_reuse_supported_python.py (97%) rename tests/{ => scripts/install}/test_install_sh_root_fhs_uv_python_path.py (97%) rename tests/{ => scripts/install}/test_install_sh_setup_wizard_tty_probe.py (98%) rename tests/{ => scripts/install}/test_install_sh_symlink_stomp.py (98%) rename tests/{ => scripts/install}/test_install_sh_termux_network_prereqs.py (92%) rename tests/{ => scripts/install}/test_install_sh_termux_python_bounds.py (99%) rename tests/{ => scripts/install}/test_install_sh_uv_lock_config.py (98%) rename tests/{ => scripts/install}/test_install_unmerged_index.py (99%) rename tests/{ => scripts}/test_run_tests_parallel.py (97%) rename tests/{ => scripts}/test_run_tests_parallel_stdio.py (97%) rename tests/{ => skills}/test_evidence_store.py (99%) delete mode 100644 tests/test_minisweagent_path.py delete mode 100644 tests/test_model_picker_scroll.py delete mode 100644 tests/test_process_loop_event_loop_warning.py create mode 100644 tests/test_tests_tree_layout.py rename tests/{test_yaml_indent_consistency_31999.py => test_yaml_indent_consistency.py} (100%) rename tests/{ => tools}/test_browser_vault.py (99%) rename tests/{ => tools}/test_compaction_tool_refresh.py (98%) rename tests/tools/{test_hub_lock_non_utf8_68053.py => test_hub_lock_non_utf8.py} (100%) rename tests/{ => tools}/test_model_tools.py (100%) rename tests/{ => tools}/test_plugin_compat_warning.py (91%) rename tests/{ => tools}/test_plugin_skills.py (100%) rename tests/{ => tools}/test_subprocess_home_isolation.py (100%) rename tests/tools/{test_94248_timeout_transport_drain.py => test_timeout_transport_drain.py} (100%) rename tests/{ => tools}/test_toolset_distributions.py (100%) rename tests/{ => tools}/test_toolsets.py (100%) rename tests/{ => tools}/test_voice_max_recording_seconds.py (100%) rename tests/tui_gateway/{test_94895_backend_heartbeat.py => test_backend_heartbeat.py} (100%) rename tests/{ => tui_gateway}/test_lazy_session_regressions.py (100%) rename tests/tui_gateway/{test_106459_routing_provenance_reopen.py => test_routing_provenance_reopen.py} (98%) rename tests/{ => tui_gateway}/test_slash_worker_watchdog.py (100%) rename tests/{ => tui_gateway}/test_tui_entry_mcp_owner.py (100%) rename tests/{ => tui_gateway}/test_tui_gateway_event_replay.py (100%) rename tests/{ => tui_gateway}/test_tui_gateway_loop_noise.py (100%) rename tests/{ => tui_gateway}/test_tui_gateway_queue_on_busy.py (100%) rename tests/{ => tui_gateway}/test_tui_gateway_server.py (100%) rename tests/{ => tui_gateway}/test_tui_gateway_server_crash_history.py (100%) rename tests/{ => tui_gateway}/test_tui_gateway_ws.py (100%) rename tests/{ => tui_gateway}/test_tui_mcp_late_refresh.py (100%) rename tests/{ => tui_gateway}/test_ws_keepalive_config.py (100%) diff --git a/.github/workflows/tests-os.yml b/.github/workflows/tests-os.yml index 2477c839f4..58374492c0 100644 --- a/.github/workflows/tests-os.yml +++ b/.github/workflows/tests-os.yml @@ -31,7 +31,7 @@ on: desktop_updater: description: >- Run the Windows desktop-update hand-off integration tests - (tests/test_desktop_update_windows_*.py). These spawn the real + (tests/scripts/desktop_update/test_desktop_update_windows_*.py). These spawn the real scripts/desktop-update/windows.ps1 and poll its loopback server, so they carry process-timing noise a shared runner amplifies; the caller gates them on the classifier's desktop_updater lane so a PR @@ -156,7 +156,7 @@ jobs: # an unbound-variable error, hence the ``${arr[@]+...}`` idiom.) EXTRA_ARGS=() if [ "${{ inputs.desktop_updater }}" != "true" ]; then - echo "desktop_updater lane off: skipping tests/test_desktop_update_windows_*.py" + echo "desktop_updater lane off: skipping tests/scripts/desktop_update/test_desktop_update_windows_*.py" EXTRA_ARGS+=(--ignore-glob='*test_desktop_update_windows_*.py') fi diff --git a/AGENTS.md b/AGENTS.md index fca7cee1d9..b8db958cd9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -330,7 +330,12 @@ scripts/run_tests.sh -v --tb=long # pytest flags pass thro `HERMES_TEST_FILE_RETRIES=0` disables). Pass-on-retry is green but printed under `⚠ FLAKY` with both outputs — a bug to fix, not noise. Timing tests must not assume a quiet runner: wall-clock bounds ≥ 2s, event-based sync, no `assert not _wait_until(...)` races. -- **Placement:** `scripts/ci/classify_changes.py` picks jobs by changed files. A Python test +- **Placement mirrors the source tree.** A test lives in `tests//` (`tests/hermes_cli/`, + `tests/agent/`, `tests/hermes_state/`, `tests/gateway/relay/`, ...); installer/updater script tests + under `tests/scripts/{install,desktop_update}/`. Only tests of root-level modules (`batch_runner`, + `utils`, `hermes_constants`, packaging) sit directly in `tests/`. No issue numbers in filenames — + cite the issue in the module docstring (`test_89315_x.py` → `test_x.py`, "Regression for #89315"). +- **Placement (CI lanes):** `scripts/ci/classify_changes.py` picks jobs by changed files. A Python test asserting about `package.json`, `package-lock.json`, `tsconfig.json`, or `.ts/.tsx/.js/ .mjs/.cjs` sources will not run on a JS-only PR (green on PR, red on `main` where the classifier fails open). Such tests belong in the vitest suite, not `tests/*.py`. diff --git a/agent/activity_tracking.py b/agent/activity_tracking.py index 0f2a385208..f456f9f448 100644 --- a/agent/activity_tracking.py +++ b/agent/activity_tracking.py @@ -49,7 +49,7 @@ class ActivityTrackingMixin: kanban heartbeat when this is a dispatcher-spawned worker, and to the durable SessionDB activity projection. ``provenance`` names special writers (compression); ``force_persist`` bypasses the SessionDB rate limit. Module-level lock helper, not ``self._liveness_activity_lock()``: doubles bind - only ``_touch_activity`` (tests/run_agent/test_session_activity_persist.py). + only ``_touch_activity`` (tests/agent/test_session_activity_persist.py). Bridge is rate-limited (60s) and best-effort — it never raises into the agent loop. See #31752. See #72016, #72039. diff --git a/agent/agent_runtime_helpers.py b/agent/agent_runtime_helpers.py index 658f156bac..4c49896d5c 100644 --- a/agent/agent_runtime_helpers.py +++ b/agent/agent_runtime_helpers.py @@ -1735,7 +1735,7 @@ def create_openai_client(agent, client_kwargs: dict, *, reason: str, shared: boo return client # TCP keepalives so dead provider connections are detected (~60s) instead of hanging in # CLOSE-WAIT. Injected into the local copy only, so each client gets its own httpx.Client; - # pinned by tests/run_agent/test_create_openai_client_reuse.py and + # pinned by tests/agent/test_create_openai_client_reuse.py and # test_sequential_chats_live.py. What IS shared across those per-client wrappers is the # connection pool: ``build_keepalive_http_client`` mounts a process-shared ``HTTPTransport`` # behind a per-client view whose ``close()`` is a no-op for the pool, so a closed wrapper @@ -2320,7 +2320,7 @@ def repair_tool_call(agent, tool_name: str) -> str | None: # character so the rest of the repair pipeline (lowercase / snake_case / fuzzy match) can resolve the # cleaned name to a real tool. Crucially we DO NOT split on whitespace: legitimate inputs like "write # file" must keep flowing through ``_norm`` -> ``write_file`` (covered by test_space_to_underscore in - # tests/run_agent/test_repair_tool_call_name.py). See #33007. + # tests/agent/test_repair_tool_call_name.py). See #33007. for _xml_sep in ('"', "'", "<", ">"): _idx = tool_name.find(_xml_sep) if _idx > 0: diff --git a/cli.py b/cli.py index dc45f5b837..e4680ce9e0 100644 --- a/cli.py +++ b/cli.py @@ -2147,7 +2147,7 @@ def _terminal_may_leak_cpr() -> bool: Delayed CPR replies (``ESC[;R`` / visible ``^[[;R``) leak into the status line and can freeze input when the reply is slow (#13870 on SSH/slow PTYs). The same race hits local POSIX TTYs - under heavy subagent / status-line load — see ``tests/cli/test_cpr_local_leak.py``. + under heavy subagent / status-line load — see ``tests/hermes_cli/test_cpr_local_leak.py``. """ return os.environ.get("PROMPT_TOOLKIT_NO_CPR", "") == "1" or sys.platform != "win32" diff --git a/hermes_cli/cli_info_mixin.py b/hermes_cli/cli_info_mixin.py index 0d7fbbd6ca..7ff260fdfe 100644 --- a/hermes_cli/cli_info_mixin.py +++ b/hermes_cli/cli_info_mixin.py @@ -459,7 +459,7 @@ class CLIInfoMixin: Dispatched from the input loop BEFORE slash routing and before anything is queued for the agent, so a bang command never becomes a turn: nothing touches ``conversation_history``, zero tokens, role alternation / prompt caching untouched by construction - (tests/cli/test_bang_shell_mode.py). Returns False when the text is not a bang command or + (tests/hermes_cli/test_bang_shell_mode.py). Returns False when the text is not a bang command or bang mode is disabled for this context (gateway/cron), so the caller routes normally. """ from cli import _rich_text_from_ansi diff --git a/hermes_cli/default_soul.py b/hermes_cli/default_soul.py index 3c99d404dd..3a19405021 100644 --- a/hermes_cli/default_soul.py +++ b/hermes_cli/default_soul.py @@ -49,7 +49,7 @@ _LEGACY_TEMPLATE_SOULS = ( "investigations." ), # ASCII-dashed variant seeded by scripts/install.ps1 (must stay pure ASCII, see - # tests/test_install_ps1_ascii_only.py); upgrading converges Windows installs on the em-dash text. + # tests/scripts/install/test_install_ps1_ascii_only.py); upgrading converges Windows installs on the em-dash text. DEFAULT_SOUL_MD.replace("\u2014", "--"), ) diff --git a/hermes_state_portability.py b/hermes_state_portability.py index 2a960ec038..7132f0b22a 100644 --- a/hermes_state_portability.py +++ b/hermes_state_portability.py @@ -534,7 +534,7 @@ class SessionPortabilityMixin: / ``last_activity_description`` / ``last_activity_provenance``) because they are part of the durable row, but import deliberately RESETS them to NULL. This asymmetry is intentional and covered by regression - (tests/gateway/test_watchdog_review_76354.py::test_s4_export_includes_activity_import_resets_it). + (tests/gateway/test_watchdog_review.py::test_s4_export_includes_activity_import_resets_it). """ if not isinstance(sessions, list): raise ValueError("sessions must be a list") diff --git a/scripts/ci/classify_changes.py b/scripts/ci/classify_changes.py index 558ccc12d5..6bf01febee 100644 --- a/scripts/ci/classify_changes.py +++ b/scripts/ci/classify_changes.py @@ -131,7 +131,7 @@ _INSTALLER_FILES = {"scripts/install.ps1", "scripts/install.cmd"} # Windows desktop-update hand-off (scripts/desktop-update/windows.ps1 + the # Electron side that launches it) and the pytest files that spawn it. _DESKTOP_UPDATER_PATHS = ("scripts/desktop-update/",) -_DESKTOP_UPDATER_TEST_PREFIX = "tests/test_desktop_update_" +_DESKTOP_UPDATER_TEST_PREFIX = "tests/scripts/desktop_update/" _DESKTOP_UPDATER_FILES = { "apps/desktop/electron/updater-process.ts", "apps/desktop/electron/managed-ssh-update.ts", diff --git a/scripts/desktop-update/posix.sh b/scripts/desktop-update/posix.sh index 063d41bbab..7f73d69beb 100755 --- a/scripts/desktop-update/posix.sh +++ b/scripts/desktop-update/posix.sh @@ -629,7 +629,7 @@ tcc_pick_update_invoke() { # sets UPDATE_INVOKE; safety net past a failed heal # ── self-tests: no update, touch nothing ──────────────────────────────────── if [ "$SELF_TEST_TCC_HEAL" -eq 1 ]; then # Runs the REAL heal + invoke selection against --install-root and reports; - # tests/test_desktop_update_tcc_heal.py drives the state matrix through it. + # tests/scripts/desktop_update/test_desktop_update_tcc_heal.py drives the state matrix through it. trap - EXIT tcc_anchor_heal "$INSTALL_ROOT/venv/bin" || true tcc_pick_update_invoke "$INSTALL_ROOT/venv/bin" diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index 4445d6d542..77eedc6d1c 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -17,7 +17,7 @@ # scripts/run_tests.sh # full suite # scripts/run_tests.sh -j 4 # cap parallelism # scripts/run_tests.sh tests/agent/ # discover only here -# scripts/run_tests.sh tests/agent/ tests/acp/ # multiple roots +# scripts/run_tests.sh tests/agent/ tests/acp_adapter/ # multiple roots # scripts/run_tests.sh tests/foo.py # single file # scripts/run_tests.sh tests/foo.py -q # path + bare pytest flag # scripts/run_tests.sh tests/foo.py -v --tb=long # bare flags "just work" diff --git a/scripts/run_tests_parallel.py b/scripts/run_tests_parallel.py index eb923cc9b4..5e20caf3cb 100755 --- a/scripts/run_tests_parallel.py +++ b/scripts/run_tests_parallel.py @@ -508,8 +508,8 @@ def _parse_pytest_summary(output: str) -> dict[str, int]: def _format_file(file: Path, repo_root: Path) -> str: """Render a test-file path for display: strip the repo-root prefix - when possible so output reads ``tests/acp/test_auth.py`` instead of - ``/home/runner/work/hermes-agent/hermes-agent/tests/acp/test_auth.py``. + when possible so output reads ``tests/acp_adapter/test_auth.py`` instead of + ``/home/runner/work/hermes-agent/hermes-agent/tests/acp_adapter/test_auth.py``. Falls back to the absolute path for anything outside the repo root. """ diff --git a/tests/acp/__init__.py b/tests/acp_adapter/__init__.py similarity index 100% rename from tests/acp/__init__.py rename to tests/acp_adapter/__init__.py diff --git a/tests/acp/conftest.py b/tests/acp_adapter/conftest.py similarity index 96% rename from tests/acp/conftest.py rename to tests/acp_adapter/conftest.py index 4d5e1a37e9..620f9d3b6e 100644 --- a/tests/acp/conftest.py +++ b/tests/acp_adapter/conftest.py @@ -1,4 +1,4 @@ -"""Shared fixtures for tests/acp. +"""Shared fixtures for tests/acp_adapter. Keeps the ACP server tests offline: ``HermesACPAgent._build_model_state`` calls ``hermes_cli.inventory.build_models_payload``, which (without this diff --git a/tests/acp/test_approval_isolation.py b/tests/acp_adapter/test_approval_isolation.py similarity index 98% rename from tests/acp/test_approval_isolation.py rename to tests/acp_adapter/test_approval_isolation.py index b45e96789a..bd3658ca7d 100644 --- a/tests/acp/test_approval_isolation.py +++ b/tests/acp_adapter/test_approval_isolation.py @@ -23,7 +23,7 @@ from tools import approval_context def _isolate_approval_state(monkeypatch): """Keep these security regression tests hermetic. - Earlier tests (e.g. tests/acp/test_permissions.py) lazily load the + Earlier tests (e.g. tests/acp_adapter/test_permissions.py) lazily load the developer's real ``~/.hermes/config.yaml`` command allowlist into ``tools.approval._permanent_approved``. If that allowlist contains a pattern like "recursive delete", ``rm -rf …`` is auto-approved before diff --git a/tests/acp/test_auth.py b/tests/acp_adapter/test_auth.py similarity index 100% rename from tests/acp/test_auth.py rename to tests/acp_adapter/test_auth.py diff --git a/tests/acp/test_edit_approval.py b/tests/acp_adapter/test_edit_approval.py similarity index 100% rename from tests/acp/test_edit_approval.py rename to tests/acp_adapter/test_edit_approval.py diff --git a/tests/acp/test_empty_session_persistence.py b/tests/acp_adapter/test_empty_session_persistence.py similarity index 100% rename from tests/acp/test_empty_session_persistence.py rename to tests/acp_adapter/test_empty_session_persistence.py diff --git a/tests/acp/test_entry.py b/tests/acp_adapter/test_entry.py similarity index 100% rename from tests/acp/test_entry.py rename to tests/acp_adapter/test_entry.py diff --git a/tests/acp/test_events.py b/tests/acp_adapter/test_events.py similarity index 100% rename from tests/acp/test_events.py rename to tests/acp_adapter/test_events.py diff --git a/tests/acp/test_mcp_e2e.py b/tests/acp_adapter/test_mcp_e2e.py similarity index 100% rename from tests/acp/test_mcp_e2e.py rename to tests/acp_adapter/test_mcp_e2e.py diff --git a/tests/acp/test_named_provider_catalogs.py b/tests/acp_adapter/test_named_provider_catalogs.py similarity index 100% rename from tests/acp/test_named_provider_catalogs.py rename to tests/acp_adapter/test_named_provider_catalogs.py diff --git a/tests/acp/test_permissions.py b/tests/acp_adapter/test_permissions.py similarity index 100% rename from tests/acp/test_permissions.py rename to tests/acp_adapter/test_permissions.py diff --git a/tests/acp/test_ping_suppression.py b/tests/acp_adapter/test_ping_suppression.py similarity index 100% rename from tests/acp/test_ping_suppression.py rename to tests/acp_adapter/test_ping_suppression.py diff --git a/tests/acp/test_server.py b/tests/acp_adapter/test_server.py similarity index 100% rename from tests/acp/test_server.py rename to tests/acp_adapter/test_server.py diff --git a/tests/acp/test_session.py b/tests/acp_adapter/test_session.py similarity index 100% rename from tests/acp/test_session.py rename to tests/acp_adapter/test_session.py diff --git a/tests/acp/test_session_db_private_access.py b/tests/acp_adapter/test_session_db_private_access.py similarity index 100% rename from tests/acp/test_session_db_private_access.py rename to tests/acp_adapter/test_session_db_private_access.py diff --git a/tests/acp/test_session_provenance.py b/tests/acp_adapter/test_session_provenance.py similarity index 100% rename from tests/acp/test_session_provenance.py rename to tests/acp_adapter/test_session_provenance.py diff --git a/tests/acp/test_tools.py b/tests/acp_adapter/test_tools.py similarity index 100% rename from tests/acp/test_tools.py rename to tests/acp_adapter/test_tools.py diff --git a/tests/run_agent/conftest.py b/tests/agent/conftest.py similarity index 96% rename from tests/run_agent/conftest.py rename to tests/agent/conftest.py index f786b8467f..5dde64145c 100644 --- a/tests/run_agent/conftest.py +++ b/tests/agent/conftest.py @@ -1,4 +1,4 @@ -"""Fast-path fixtures shared across tests/run_agent/. +"""Fast-path fixtures shared across tests/agent/. Many tests in this directory exercise the retry/backoff paths in the agent loop. Production code uses ``jittered_backoff(base_delay=5.0)`` diff --git a/tests/run_agent/repro_48013_image_shrink_brick.py b/tests/agent/repro_image_shrink_brick.py similarity index 98% rename from tests/run_agent/repro_48013_image_shrink_brick.py rename to tests/agent/repro_image_shrink_brick.py index ee099f48dc..5c2e17ca19 100644 --- a/tests/run_agent/repro_48013_image_shrink_brick.py +++ b/tests/agent/repro_image_shrink_brick.py @@ -16,11 +16,11 @@ brought under the cap and that the helper reports success. Run directly to see a human-readable report: - python tests/run_agent/repro_48013_image_shrink_brick.py + python tests/agent/repro_image_shrink_brick.py Or as a pytest smoke test (skipped automatically when Pillow is absent): - scripts/run_tests.sh tests/run_agent/repro_48013_image_shrink_brick.py + scripts/run_tests.sh tests/agent/repro_image_shrink_brick.py """ from __future__ import annotations diff --git a/tests/run_agent/test_31273_402_not_retried.py b/tests/agent/test_402_not_retried.py similarity index 100% rename from tests/run_agent/test_31273_402_not_retried.py rename to tests/agent/test_402_not_retried.py diff --git a/tests/run_agent/test_413_compression.py b/tests/agent/test_413_compression.py similarity index 100% rename from tests/run_agent/test_413_compression.py rename to tests/agent/test_413_compression.py diff --git a/tests/run_agent/test_860_dedup.py b/tests/agent/test_860_dedup.py similarity index 100% rename from tests/run_agent/test_860_dedup.py rename to tests/agent/test_860_dedup.py diff --git a/tests/test_account_usage.py b/tests/agent/test_account_usage_fetch.py similarity index 100% rename from tests/test_account_usage.py rename to tests/agent/test_account_usage_fetch.py diff --git a/tests/run_agent/test_agent_guardrails.py b/tests/agent/test_agent_guardrails.py similarity index 100% rename from tests/run_agent/test_agent_guardrails.py rename to tests/agent/test_agent_guardrails.py diff --git a/tests/run_agent/test_anthropic_mid_tool_call_drop.py b/tests/agent/test_anthropic_mid_tool_call_drop.py similarity index 100% rename from tests/run_agent/test_anthropic_mid_tool_call_drop.py rename to tests/agent/test_anthropic_mid_tool_call_drop.py diff --git a/tests/run_agent/test_anthropic_prompt_cache_policy.py b/tests/agent/test_anthropic_prompt_cache_policy.py similarity index 100% rename from tests/run_agent/test_anthropic_prompt_cache_policy.py rename to tests/agent/test_anthropic_prompt_cache_policy.py diff --git a/tests/run_agent/test_anthropic_response_header_capture.py b/tests/agent/test_anthropic_response_header_capture.py similarity index 100% rename from tests/run_agent/test_anthropic_response_header_capture.py rename to tests/agent/test_anthropic_response_header_capture.py diff --git a/tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py b/tests/agent/test_anthropic_stream_pool_cleanup.py similarity index 100% rename from tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py rename to tests/agent/test_anthropic_stream_pool_cleanup.py diff --git a/tests/run_agent/test_anthropic_third_party_oauth_guard.py b/tests/agent/test_anthropic_third_party_oauth_guard.py similarity index 100% rename from tests/run_agent/test_anthropic_third_party_oauth_guard.py rename to tests/agent/test_anthropic_third_party_oauth_guard.py diff --git a/tests/run_agent/test_anthropic_truncation_continuation.py b/tests/agent/test_anthropic_truncation_continuation.py similarity index 100% rename from tests/run_agent/test_anthropic_truncation_continuation.py rename to tests/agent/test_anthropic_truncation_continuation.py diff --git a/tests/run_agent/test_api_max_retries_config.py b/tests/agent/test_api_max_retries_config.py similarity index 100% rename from tests/run_agent/test_api_max_retries_config.py rename to tests/agent/test_api_max_retries_config.py diff --git a/tests/run_agent/test_async_httpx_del_neuter.py b/tests/agent/test_async_httpx_del_neuter.py similarity index 100% rename from tests/run_agent/test_async_httpx_del_neuter.py rename to tests/agent/test_async_httpx_del_neuter.py diff --git a/tests/run_agent/test_auth_provider_failover.py b/tests/agent/test_auth_provider_failover.py similarity index 100% rename from tests/run_agent/test_auth_provider_failover.py rename to tests/agent/test_auth_provider_failover.py diff --git a/tests/run_agent/test_authorization_gate.py b/tests/agent/test_authorization_gate.py similarity index 100% rename from tests/run_agent/test_authorization_gate.py rename to tests/agent/test_authorization_gate.py diff --git a/tests/agent/test_auxiliary_client_base_url_host_validation_52608.py b/tests/agent/test_auxiliary_client_base_url_host_validation.py similarity index 100% rename from tests/agent/test_auxiliary_client_base_url_host_validation_52608.py rename to tests/agent/test_auxiliary_client_base_url_host_validation.py diff --git a/tests/agent/test_auxiliary_user_default_headers.py b/tests/agent/test_auxiliary_user_default_headers.py index 2e9fafdb16..aff5b2ecc4 100644 --- a/tests/agent/test_auxiliary_user_default_headers.py +++ b/tests/agent/test_auxiliary_user_default_headers.py @@ -1,6 +1,6 @@ """Tests for user-configured ``model.default_headers`` in the auxiliary client. -Companion to ``tests/run_agent/test_provider_attribution_headers.py`` (which +Companion to ``tests/agent/test_provider_attribution_headers.py`` (which covers the main agent client). The main agent turn and the auxiliary client (title generation, context compression, vision routing) build separate OpenAI clients, so a ``custom`` endpoint behind a gateway/WAF that rejects the OpenAI diff --git a/tests/run_agent/test_background_review.py b/tests/agent/test_background_review.py similarity index 100% rename from tests/run_agent/test_background_review.py rename to tests/agent/test_background_review.py diff --git a/tests/run_agent/test_background_review_cache_parity.py b/tests/agent/test_background_review_cache_parity.py similarity index 100% rename from tests/run_agent/test_background_review_cache_parity.py rename to tests/agent/test_background_review_cache_parity.py diff --git a/tests/run_agent/test_background_review_cost_controls.py b/tests/agent/test_background_review_cost_controls.py similarity index 100% rename from tests/run_agent/test_background_review_cost_controls.py rename to tests/agent/test_background_review_cost_controls.py diff --git a/tests/run_agent/test_background_review_input_budget.py b/tests/agent/test_background_review_input_budget.py similarity index 100% rename from tests/run_agent/test_background_review_input_budget.py rename to tests/agent/test_background_review_input_budget.py diff --git a/tests/test_background_review_list_shapes.py b/tests/agent/test_background_review_list_shapes.py similarity index 99% rename from tests/test_background_review_list_shapes.py rename to tests/agent/test_background_review_list_shapes.py index cc22d2825a..1589774b53 100644 --- a/tests/test_background_review_list_shapes.py +++ b/tests/agent/test_background_review_list_shapes.py @@ -43,7 +43,7 @@ import sys import types -REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) def _isolate_hermes_home(): diff --git a/tests/run_agent/test_background_review_summary.py b/tests/agent/test_background_review_summary.py similarity index 100% rename from tests/run_agent/test_background_review_summary.py rename to tests/agent/test_background_review_summary.py diff --git a/tests/run_agent/test_background_review_toolset_restriction.py b/tests/agent/test_background_review_toolset_restriction.py similarity index 100% rename from tests/run_agent/test_background_review_toolset_restriction.py rename to tests/agent/test_background_review_toolset_restriction.py diff --git a/tests/test_bitwarden_secrets.py b/tests/agent/test_bitwarden_secrets.py similarity index 99% rename from tests/test_bitwarden_secrets.py rename to tests/agent/test_bitwarden_secrets.py index 420605416c..f304d64740 100644 --- a/tests/test_bitwarden_secrets.py +++ b/tests/agent/test_bitwarden_secrets.py @@ -24,7 +24,7 @@ import pytest # Make the worktree importable without depending on the installed wheel. -ROOT = Path(__file__).resolve().parents[1] +ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) diff --git a/tests/run_agent/test_callable_api_key.py b/tests/agent/test_callable_api_key.py similarity index 100% rename from tests/run_agent/test_callable_api_key.py rename to tests/agent/test_callable_api_key.py diff --git a/tests/agent/test_cascading_interrupt_6600.py b/tests/agent/test_cascading_interrupt.py similarity index 100% rename from tests/agent/test_cascading_interrupt_6600.py rename to tests/agent/test_cascading_interrupt.py diff --git a/tests/run_agent/test_codex_app_server_compaction.py b/tests/agent/test_codex_app_server_compaction.py similarity index 100% rename from tests/run_agent/test_codex_app_server_compaction.py rename to tests/agent/test_codex_app_server_compaction.py diff --git a/tests/run_agent/test_codex_app_server_integration.py b/tests/agent/test_codex_app_server_integration.py similarity index 100% rename from tests/run_agent/test_codex_app_server_integration.py rename to tests/agent/test_codex_app_server_integration.py diff --git a/tests/run_agent/test_codex_app_server_lifecycle.py b/tests/agent/test_codex_app_server_lifecycle.py similarity index 100% rename from tests/run_agent/test_codex_app_server_lifecycle.py rename to tests/agent/test_codex_app_server_lifecycle.py diff --git a/tests/run_agent/test_codex_multimodal_tool_result.py b/tests/agent/test_codex_multimodal_tool_result.py similarity index 100% rename from tests/run_agent/test_codex_multimodal_tool_result.py rename to tests/agent/test_codex_multimodal_tool_result.py diff --git a/tests/run_agent/test_codex_no_tools_nonetype.py b/tests/agent/test_codex_no_tools_nonetype.py similarity index 100% rename from tests/run_agent/test_codex_no_tools_nonetype.py rename to tests/agent/test_codex_no_tools_nonetype.py diff --git a/tests/run_agent/test_codex_sdk_transform_bypass.py b/tests/agent/test_codex_sdk_transform_bypass.py similarity index 100% rename from tests/run_agent/test_codex_sdk_transform_bypass.py rename to tests/agent/test_codex_sdk_transform_bypass.py diff --git a/tests/run_agent/test_codex_silent_hang_hint.py b/tests/agent/test_codex_silent_hang_hint.py similarity index 100% rename from tests/run_agent/test_codex_silent_hang_hint.py rename to tests/agent/test_codex_silent_hang_hint.py diff --git a/tests/run_agent/test_codex_xai_oauth_recovery.py b/tests/agent/test_codex_xai_oauth_recovery.py similarity index 100% rename from tests/run_agent/test_codex_xai_oauth_recovery.py rename to tests/agent/test_codex_xai_oauth_recovery.py diff --git a/tests/test_command_secret_source.py b/tests/agent/test_command_secret_source.py similarity index 99% rename from tests/test_command_secret_source.py rename to tests/agent/test_command_secret_source.py index 2cfd9e5c47..6e05a884f1 100644 --- a/tests/test_command_secret_source.py +++ b/tests/agent/test_command_secret_source.py @@ -27,7 +27,7 @@ from pathlib import Path import pytest -ROOT = Path(__file__).resolve().parents[1] +ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) diff --git a/tests/run_agent/test_commit_memory_session_context_engine.py b/tests/agent/test_commit_memory_session_context_engine.py similarity index 100% rename from tests/run_agent/test_commit_memory_session_context_engine.py rename to tests/agent/test_commit_memory_session_context_engine.py diff --git a/tests/test_compaction_prompt_rebuild.py b/tests/agent/test_compaction_prompt_rebuild.py similarity index 100% rename from tests/test_compaction_prompt_rebuild.py rename to tests/agent/test_compaction_prompt_rebuild.py diff --git a/tests/run_agent/test_compress_context_fallback_shim.py b/tests/agent/test_compress_context_fallback_shim.py similarity index 100% rename from tests/run_agent/test_compress_context_fallback_shim.py rename to tests/agent/test_compress_context_fallback_shim.py diff --git a/tests/run_agent/test_compress_focus_plugin_fallback.py b/tests/agent/test_compress_focus_plugin_fallback.py similarity index 100% rename from tests/run_agent/test_compress_focus_plugin_fallback.py rename to tests/agent/test_compress_focus_plugin_fallback.py diff --git a/tests/run_agent/test_compression_abort_state_reset.py b/tests/agent/test_compression_abort_state_reset.py similarity index 100% rename from tests/run_agent/test_compression_abort_state_reset.py rename to tests/agent/test_compression_abort_state_reset.py diff --git a/tests/run_agent/test_compression_boundary.py b/tests/agent/test_compression_boundary.py similarity index 100% rename from tests/run_agent/test_compression_boundary.py rename to tests/agent/test_compression_boundary.py diff --git a/tests/run_agent/test_compression_boundary_hook.py b/tests/agent/test_compression_boundary_hook.py similarity index 100% rename from tests/run_agent/test_compression_boundary_hook.py rename to tests/agent/test_compression_boundary_hook.py diff --git a/tests/run_agent/test_compression_budget_rearm.py b/tests/agent/test_compression_budget_rearm.py similarity index 100% rename from tests/run_agent/test_compression_budget_rearm.py rename to tests/agent/test_compression_budget_rearm.py diff --git a/tests/run_agent/test_compression_budget_refund.py b/tests/agent/test_compression_budget_refund.py similarity index 100% rename from tests/run_agent/test_compression_budget_refund.py rename to tests/agent/test_compression_budget_refund.py diff --git a/tests/run_agent/test_compression_closed_adoption.py b/tests/agent/test_compression_closed_adoption.py similarity index 100% rename from tests/run_agent/test_compression_closed_adoption.py rename to tests/agent/test_compression_closed_adoption.py diff --git a/tests/agent/test_compression_count_warning_36908.py b/tests/agent/test_compression_count_warning.py similarity index 100% rename from tests/agent/test_compression_count_warning_36908.py rename to tests/agent/test_compression_count_warning.py diff --git a/tests/run_agent/test_compression_feasibility.py b/tests/agent/test_compression_feasibility.py similarity index 100% rename from tests/run_agent/test_compression_feasibility.py rename to tests/agent/test_compression_feasibility.py diff --git a/tests/run_agent/test_compression_lock_defer.py b/tests/agent/test_compression_lock_defer.py similarity index 99% rename from tests/run_agent/test_compression_lock_defer.py rename to tests/agent/test_compression_lock_defer.py index 1eb5e69553..4e8459ad69 100644 --- a/tests/run_agent/test_compression_lock_defer.py +++ b/tests/agent/test_compression_lock_defer.py @@ -35,7 +35,7 @@ LOCK_HOLDER = "pid=4242:tid=1:agent=deadbeef:nonce=abcd1234" # --------------------------------------------------------------------------- -# Helpers (mirrors tests/run_agent/test_413_compression.py) +# Helpers (mirrors tests/agent/test_413_compression.py) # --------------------------------------------------------------------------- diff --git a/tests/run_agent/test_compression_persistence.py b/tests/agent/test_compression_persistence.py similarity index 100% rename from tests/run_agent/test_compression_persistence.py rename to tests/agent/test_compression_persistence.py diff --git a/tests/agent/test_compression_review_76354.py b/tests/agent/test_compression_review.py similarity index 100% rename from tests/agent/test_compression_review_76354.py rename to tests/agent/test_compression_review.py diff --git a/tests/agent/test_compression_stall_fallback_78981.py b/tests/agent/test_compression_stall_fallback.py similarity index 100% rename from tests/agent/test_compression_stall_fallback_78981.py rename to tests/agent/test_compression_stall_fallback.py diff --git a/tests/agent/test_compression_stall_interrupt_96775.py b/tests/agent/test_compression_stall_interrupt.py similarity index 100% rename from tests/agent/test_compression_stall_interrupt_96775.py rename to tests/agent/test_compression_stall_interrupt.py diff --git a/tests/run_agent/test_compression_timeout_terminal.py b/tests/agent/test_compression_timeout_terminal.py similarity index 100% rename from tests/run_agent/test_compression_timeout_terminal.py rename to tests/agent/test_compression_timeout_terminal.py diff --git a/tests/run_agent/test_compression_trigger_excludes_reasoning.py b/tests/agent/test_compression_trigger_excludes_reasoning.py similarity index 100% rename from tests/run_agent/test_compression_trigger_excludes_reasoning.py rename to tests/agent/test_compression_trigger_excludes_reasoning.py diff --git a/tests/agent/test_compression_worker_isolation_76354.py b/tests/agent/test_compression_worker_isolation.py similarity index 100% rename from tests/agent/test_compression_worker_isolation_76354.py rename to tests/agent/test_compression_worker_isolation.py diff --git a/tests/run_agent/test_compressor_fallback_update.py b/tests/agent/test_compressor_fallback_update.py similarity index 100% rename from tests/run_agent/test_compressor_fallback_update.py rename to tests/agent/test_compressor_fallback_update.py diff --git a/tests/run_agent/test_concurrent_interrupt.py b/tests/agent/test_concurrent_interrupt.py similarity index 100% rename from tests/run_agent/test_concurrent_interrupt.py rename to tests/agent/test_concurrent_interrupt.py diff --git a/tests/run_agent/test_18028_content_policy_blocked.py b/tests/agent/test_content_policy_blocked.py similarity index 100% rename from tests/run_agent/test_18028_content_policy_blocked.py rename to tests/agent/test_content_policy_blocked.py diff --git a/tests/run_agent/test_1630_context_overflow_loop.py b/tests/agent/test_context_overflow_loop.py similarity index 100% rename from tests/run_agent/test_1630_context_overflow_loop.py rename to tests/agent/test_context_overflow_loop.py diff --git a/tests/run_agent/test_context_token_tracking.py b/tests/agent/test_context_token_tracking.py similarity index 100% rename from tests/run_agent/test_context_token_tracking.py rename to tests/agent/test_context_token_tracking.py diff --git a/tests/run_agent/test_continuation_ceiling_wedge.py b/tests/agent/test_continuation_ceiling_wedge.py similarity index 100% rename from tests/run_agent/test_continuation_ceiling_wedge.py rename to tests/agent/test_continuation_ceiling_wedge.py diff --git a/tests/run_agent/test_continuation_repetition_guard.py b/tests/agent/test_continuation_repetition_guard.py similarity index 100% rename from tests/run_agent/test_continuation_repetition_guard.py rename to tests/agent/test_continuation_repetition_guard.py diff --git a/tests/run_agent/test_conversation_fallback_state.py b/tests/agent/test_conversation_fallback_state.py similarity index 100% rename from tests/run_agent/test_conversation_fallback_state.py rename to tests/agent/test_conversation_fallback_state.py diff --git a/tests/test_copilot_initiator.py b/tests/agent/test_copilot_initiator.py similarity index 100% rename from tests/test_copilot_initiator.py rename to tests/agent/test_copilot_initiator.py diff --git a/tests/run_agent/test_copilot_native_vision_headers.py b/tests/agent/test_copilot_native_vision_headers.py similarity index 100% rename from tests/run_agent/test_copilot_native_vision_headers.py rename to tests/agent/test_copilot_native_vision_headers.py diff --git a/tests/run_agent/test_corruption_recovery_guidance.py b/tests/agent/test_corruption_recovery_guidance.py similarity index 100% rename from tests/run_agent/test_corruption_recovery_guidance.py rename to tests/agent/test_corruption_recovery_guidance.py diff --git a/tests/run_agent/test_create_openai_client_disables_sdk_retries.py b/tests/agent/test_create_openai_client_disables_sdk_retries.py similarity index 100% rename from tests/run_agent/test_create_openai_client_disables_sdk_retries.py rename to tests/agent/test_create_openai_client_disables_sdk_retries.py diff --git a/tests/run_agent/test_create_openai_client_kwargs_isolation.py b/tests/agent/test_create_openai_client_kwargs_isolation.py similarity index 100% rename from tests/run_agent/test_create_openai_client_kwargs_isolation.py rename to tests/agent/test_create_openai_client_kwargs_isolation.py diff --git a/tests/run_agent/test_create_openai_client_proxy_env.py b/tests/agent/test_create_openai_client_proxy_env.py similarity index 100% rename from tests/run_agent/test_create_openai_client_proxy_env.py rename to tests/agent/test_create_openai_client_proxy_env.py diff --git a/tests/run_agent/test_create_openai_client_reuse.py b/tests/agent/test_create_openai_client_reuse.py similarity index 100% rename from tests/run_agent/test_create_openai_client_reuse.py rename to tests/agent/test_create_openai_client_reuse.py diff --git a/tests/run_agent/test_create_openai_client_ssl_verify.py b/tests/agent/test_create_openai_client_ssl_verify.py similarity index 100% rename from tests/run_agent/test_create_openai_client_ssl_verify.py rename to tests/agent/test_create_openai_client_ssl_verify.py diff --git a/tests/run_agent/test_63425_credential_pool_auto_detect.py b/tests/agent/test_credential_pool_auto_detect.py similarity index 100% rename from tests/run_agent/test_63425_credential_pool_auto_detect.py rename to tests/agent/test_credential_pool_auto_detect.py diff --git a/tests/run_agent/test_credential_pool_interrupt.py b/tests/agent/test_credential_pool_interrupt.py similarity index 100% rename from tests/run_agent/test_credential_pool_interrupt.py rename to tests/agent/test_credential_pool_interrupt.py diff --git a/tests/run_agent/test_credential_rotation_route_settings.py b/tests/agent/test_credential_rotation_route_settings.py similarity index 100% rename from tests/run_agent/test_credential_rotation_route_settings.py rename to tests/agent/test_credential_rotation_route_settings.py diff --git a/tests/run_agent/test_credits_notices_toggle.py b/tests/agent/test_credits_notices_toggle.py similarity index 100% rename from tests/run_agent/test_credits_notices_toggle.py rename to tests/agent/test_credits_notices_toggle.py diff --git a/tests/agent/test_cron_inflight_prompt_reappend_100818.py b/tests/agent/test_cron_inflight_prompt_reappend.py similarity index 100% rename from tests/agent/test_cron_inflight_prompt_reappend_100818.py rename to tests/agent/test_cron_inflight_prompt_reappend.py diff --git a/tests/agent/test_cron_inline_api_call_62151.py b/tests/agent/test_cron_inline_api_call.py similarity index 100% rename from tests/agent/test_cron_inline_api_call_62151.py rename to tests/agent/test_cron_inline_api_call.py diff --git a/tests/run_agent/test_cross_process_turn_lease.py b/tests/agent/test_cross_process_turn_lease.py similarity index 100% rename from tests/run_agent/test_cross_process_turn_lease.py rename to tests/agent/test_cross_process_turn_lease.py diff --git a/tests/test_ctx_halving_fix.py b/tests/agent/test_ctx_halving_fix.py similarity index 100% rename from tests/test_ctx_halving_fix.py rename to tests/agent/test_ctx_halving_fix.py diff --git a/tests/run_agent/test_custom_provider_extra_headers_client.py b/tests/agent/test_custom_provider_extra_headers_client.py similarity index 100% rename from tests/run_agent/test_custom_provider_extra_headers_client.py rename to tests/agent/test_custom_provider_extra_headers_client.py diff --git a/tests/run_agent/test_deepseek_reasoning_content_echo.py b/tests/agent/test_deepseek_reasoning_content_echo.py similarity index 100% rename from tests/run_agent/test_deepseek_reasoning_content_echo.py rename to tests/agent/test_deepseek_reasoning_content_echo.py diff --git a/tests/run_agent/test_deepseek_v4_thinking_live.py b/tests/agent/test_deepseek_v4_thinking_live.py similarity index 98% rename from tests/run_agent/test_deepseek_v4_thinking_live.py rename to tests/agent/test_deepseek_v4_thinking_live.py index b938c27413..9092333a23 100644 --- a/tests/run_agent/test_deepseek_v4_thinking_live.py +++ b/tests/agent/test_deepseek_v4_thinking_live.py @@ -1,7 +1,7 @@ """Live DeepSeek V4 thinking-mode tool-call replay smoke test. Opt-in only: - HERMES_LIVE_TESTS=1 pytest tests/run_agent/test_deepseek_v4_thinking_live.py -q + HERMES_LIVE_TESTS=1 pytest tests/agent/test_deepseek_v4_thinking_live.py -q Requires DEEPSEEK_API_KEY in the process environment. The key is captured at module import time because tests/conftest.py intentionally removes credential diff --git a/tests/run_agent/test_dict_tool_call_args.py b/tests/agent/test_dict_tool_call_args.py similarity index 100% rename from tests/run_agent/test_dict_tool_call_args.py rename to tests/agent/test_dict_tool_call_args.py diff --git a/tests/run_agent/test_direct_contexts_stream_inline.py b/tests/agent/test_direct_contexts_stream_inline.py similarity index 100% rename from tests/run_agent/test_direct_contexts_stream_inline.py rename to tests/agent/test_direct_contexts_stream_inline.py diff --git a/tests/run_agent/test_dropped_tool_call_recovery.py b/tests/agent/test_dropped_tool_call_recovery.py similarity index 100% rename from tests/run_agent/test_dropped_tool_call_recovery.py rename to tests/agent/test_dropped_tool_call_recovery.py diff --git a/tests/run_agent/test_empty_response_recovery_persistence.py b/tests/agent/test_empty_response_recovery_persistence.py similarity index 100% rename from tests/run_agent/test_empty_response_recovery_persistence.py rename to tests/agent/test_empty_response_recovery_persistence.py diff --git a/tests/run_agent/test_empty_terminal_reasoning_surface.py b/tests/agent/test_empty_terminal_reasoning_surface.py similarity index 100% rename from tests/run_agent/test_empty_terminal_reasoning_surface.py rename to tests/agent/test_empty_terminal_reasoning_surface.py diff --git a/tests/run_agent/test_entitlement_fail_closed.py b/tests/agent/test_entitlement_fail_closed.py similarity index 100% rename from tests/run_agent/test_entitlement_fail_closed.py rename to tests/agent/test_entitlement_fail_closed.py diff --git a/tests/run_agent/test_env_credential_turn_refresh.py b/tests/agent/test_env_credential_turn_refresh.py similarity index 100% rename from tests/run_agent/test_env_credential_turn_refresh.py rename to tests/agent/test_env_credential_turn_refresh.py diff --git a/tests/test_env_loader_applied_homes.py b/tests/agent/test_env_loader_applied_homes.py similarity index 100% rename from tests/test_env_loader_applied_homes.py rename to tests/agent/test_env_loader_applied_homes.py diff --git a/tests/test_env_loader_secret_sources.py b/tests/agent/test_env_loader_secret_sources.py similarity index 99% rename from tests/test_env_loader_secret_sources.py rename to tests/agent/test_env_loader_secret_sources.py index ff4805064a..3ae55b94c1 100644 --- a/tests/test_env_loader_secret_sources.py +++ b/tests/agent/test_env_loader_secret_sources.py @@ -14,7 +14,7 @@ from pathlib import Path import pytest -ROOT = Path(__file__).resolve().parents[1] +ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) diff --git a/tests/agent/test_estimator_parity_84371.py b/tests/agent/test_estimator_parity.py similarity index 100% rename from tests/agent/test_estimator_parity_84371.py rename to tests/agent/test_estimator_parity.py diff --git a/tests/run_agent/test_exit_cleanup_interrupt.py b/tests/agent/test_exit_cleanup_interrupt.py similarity index 100% rename from tests/run_agent/test_exit_cleanup_interrupt.py rename to tests/agent/test_exit_cleanup_interrupt.py diff --git a/tests/run_agent/test_export_current_turn_boundary.py b/tests/agent/test_export_current_turn_boundary.py similarity index 100% rename from tests/run_agent/test_export_current_turn_boundary.py rename to tests/agent/test_export_current_turn_boundary.py diff --git a/tests/run_agent/test_32646_fallback_429_after_timeout.py b/tests/agent/test_fallback_429_after_timeout.py similarity index 100% rename from tests/run_agent/test_32646_fallback_429_after_timeout.py rename to tests/agent/test_fallback_429_after_timeout.py diff --git a/tests/run_agent/test_fallback_api_mode_preservation.py b/tests/agent/test_fallback_api_mode_preservation.py similarity index 100% rename from tests/run_agent/test_fallback_api_mode_preservation.py rename to tests/agent/test_fallback_api_mode_preservation.py diff --git a/tests/run_agent/test_fallback_credential_isolation.py b/tests/agent/test_fallback_credential_isolation.py similarity index 100% rename from tests/run_agent/test_fallback_credential_isolation.py rename to tests/agent/test_fallback_credential_isolation.py diff --git a/tests/run_agent/test_24996_fallback_exhaustion_cooldown.py b/tests/agent/test_fallback_exhaustion_cooldown.py similarity index 100% rename from tests/run_agent/test_24996_fallback_exhaustion_cooldown.py rename to tests/agent/test_fallback_exhaustion_cooldown.py diff --git a/tests/run_agent/test_fallback_reasoning_override.py b/tests/agent/test_fallback_reasoning_override.py similarity index 100% rename from tests/run_agent/test_fallback_reasoning_override.py rename to tests/agent/test_fallback_reasoning_override.py diff --git a/tests/run_agent/test_file_mutation_verifier.py b/tests/agent/test_file_mutation_verifier.py similarity index 100% rename from tests/run_agent/test_file_mutation_verifier.py rename to tests/agent/test_file_mutation_verifier.py diff --git a/tests/run_agent/test_fireworks_live.py b/tests/agent/test_fireworks_live.py similarity index 97% rename from tests/run_agent/test_fireworks_live.py rename to tests/agent/test_fireworks_live.py index ad7864c852..970ef45662 100644 --- a/tests/run_agent/test_fireworks_live.py +++ b/tests/agent/test_fireworks_live.py @@ -2,7 +2,7 @@ Opt-in only: HERMES_LIVE_TESTS=1 FIREWORKS_API_KEY=fw_... \\ - pytest tests/run_agent/test_fireworks_live.py -q + pytest tests/agent/test_fireworks_live.py -q Unlike a bare OpenAI() client pointed at the endpoint, this drives Hermes' own provider resolution — ``resolve_provider_client('fireworks')`` — so it diff --git a/tests/run_agent/test_first_chunk_at_hook.py b/tests/agent/test_first_chunk_at_hook.py similarity index 100% rename from tests/run_agent/test_first_chunk_at_hook.py rename to tests/agent/test_first_chunk_at_hook.py diff --git a/tests/run_agent/test_flush_diverts_on_corrupt_state_db.py b/tests/agent/test_flush_diverts_on_corrupt_state_db.py similarity index 100% rename from tests/run_agent/test_flush_diverts_on_corrupt_state_db.py rename to tests/agent/test_flush_diverts_on_corrupt_state_db.py diff --git a/tests/run_agent/test_gemini_native_reservation.py b/tests/agent/test_gemini_native_reservation.py similarity index 100% rename from tests/run_agent/test_gemini_native_reservation.py rename to tests/agent/test_gemini_native_reservation.py diff --git a/tests/test_hermetic_side_effect_guards.py b/tests/agent/test_hermetic_side_effect_guards.py similarity index 100% rename from tests/test_hermetic_side_effect_guards.py rename to tests/agent/test_hermetic_side_effect_guards.py diff --git a/tests/run_agent/test_identity_flush.py b/tests/agent/test_identity_flush.py similarity index 100% rename from tests/run_agent/test_identity_flush.py rename to tests/agent/test_identity_flush.py diff --git a/tests/run_agent/test_69078_image_corrupt_recovery.py b/tests/agent/test_image_corrupt_recovery.py similarity index 99% rename from tests/run_agent/test_69078_image_corrupt_recovery.py rename to tests/agent/test_image_corrupt_recovery.py index 51b834fa1a..400b0aab0b 100644 --- a/tests/run_agent/test_69078_image_corrupt_recovery.py +++ b/tests/agent/test_image_corrupt_recovery.py @@ -214,7 +214,7 @@ def _mock_response(content: str): def _make_agent(): """Build a minimal AIAgent, mirroring the idiom in - tests/run_agent/test_32646_fallback_429_after_timeout.py.""" + tests/agent/test_fallback_429_after_timeout.py.""" from run_agent import AIAgent with ( diff --git a/tests/run_agent/test_image_generate_parallel.py b/tests/agent/test_image_generate_parallel.py similarity index 100% rename from tests/run_agent/test_image_generate_parallel.py rename to tests/agent/test_image_generate_parallel.py diff --git a/tests/run_agent/test_image_rejection_fallback.py b/tests/agent/test_image_rejection_fallback.py similarity index 100% rename from tests/run_agent/test_image_rejection_fallback.py rename to tests/agent/test_image_rejection_fallback.py diff --git a/tests/run_agent/test_image_shrink_recovery.py b/tests/agent/test_image_shrink_recovery.py similarity index 100% rename from tests/run_agent/test_image_shrink_recovery.py rename to tests/agent/test_image_shrink_recovery.py diff --git a/tests/run_agent/test_in_place_commit_rollback_99477.py b/tests/agent/test_in_place_commit_rollback.py similarity index 100% rename from tests/run_agent/test_in_place_commit_rollback_99477.py rename to tests/agent/test_in_place_commit_rollback.py diff --git a/tests/run_agent/test_in_place_compaction.py b/tests/agent/test_in_place_compaction.py similarity index 100% rename from tests/run_agent/test_in_place_compaction.py rename to tests/agent/test_in_place_compaction.py diff --git a/tests/run_agent/test_in_place_persist_marker_98450.py b/tests/agent/test_in_place_persist_marker.py similarity index 100% rename from tests/run_agent/test_in_place_persist_marker_98450.py rename to tests/agent/test_in_place_persist_marker.py diff --git a/tests/run_agent/test_infinite_compaction_loop.py b/tests/agent/test_infinite_compaction_loop.py similarity index 100% rename from tests/run_agent/test_infinite_compaction_loop.py rename to tests/agent/test_infinite_compaction_loop.py diff --git a/tests/run_agent/test_init_fallback_on_exhausted_pool.py b/tests/agent/test_init_fallback_on_exhausted_pool.py similarity index 100% rename from tests/run_agent/test_init_fallback_on_exhausted_pool.py rename to tests/agent/test_init_fallback_on_exhausted_pool.py diff --git a/tests/run_agent/test_interactive_interrupt.py b/tests/agent/test_interactive_interrupt.py similarity index 100% rename from tests/run_agent/test_interactive_interrupt.py rename to tests/agent/test_interactive_interrupt.py diff --git a/tests/run_agent/test_interpreter_shutdown_turn_exit.py b/tests/agent/test_interpreter_shutdown_turn_exit.py similarity index 100% rename from tests/run_agent/test_interpreter_shutdown_turn_exit.py rename to tests/agent/test_interpreter_shutdown_turn_exit.py diff --git a/tests/run_agent/test_interrupt_propagation.py b/tests/agent/test_interrupt_propagation.py similarity index 100% rename from tests/run_agent/test_interrupt_propagation.py rename to tests/agent/test_interrupt_propagation.py diff --git a/tests/run_agent/test_invalid_context_length_warning.py b/tests/agent/test_invalid_context_length_warning.py similarity index 100% rename from tests/run_agent/test_invalid_context_length_warning.py rename to tests/agent/test_invalid_context_length_warning.py diff --git a/tests/test_iron_proxy.py b/tests/agent/test_iron_proxy.py similarity index 99% rename from tests/test_iron_proxy.py rename to tests/agent/test_iron_proxy.py index cdd563041b..995c771f62 100644 --- a/tests/test_iron_proxy.py +++ b/tests/agent/test_iron_proxy.py @@ -7,7 +7,7 @@ extraction + checksum verification fully mocked), the subprocess lifecycle docker backend's egress arg builder. Live network and the real ``iron-proxy`` binary are NEVER touched. See -``tests/test_iron_proxy_e2e.py`` (gated behind a marker) for the real-binary +``tests/agent/test_iron_proxy_e2e.py`` (gated behind a marker) for the real-binary smoke test. """ diff --git a/tests/test_iron_proxy_e2e.py b/tests/agent/test_iron_proxy_e2e.py similarity index 100% rename from tests/test_iron_proxy_e2e.py rename to tests/agent/test_iron_proxy_e2e.py diff --git a/tests/run_agent/test_iteration_budget_race.py b/tests/agent/test_iteration_budget_race.py similarity index 100% rename from tests/run_agent/test_iteration_budget_race.py rename to tests/agent/test_iteration_budget_race.py diff --git a/tests/run_agent/test_jsondecodeerror_retryable.py b/tests/agent/test_jsondecodeerror_retryable.py similarity index 100% rename from tests/run_agent/test_jsondecodeerror_retryable.py rename to tests/agent/test_jsondecodeerror_retryable.py diff --git a/tests/run_agent/test_last_reasoning_per_turn.py b/tests/agent/test_last_reasoning_per_turn.py similarity index 100% rename from tests/run_agent/test_last_reasoning_per_turn.py rename to tests/agent/test_last_reasoning_per_turn.py diff --git a/tests/run_agent/test_length_continuation_thinking_exhaustion.py b/tests/agent/test_length_continuation_thinking_exhaustion.py similarity index 100% rename from tests/run_agent/test_length_continuation_thinking_exhaustion.py rename to tests/agent/test_length_continuation_thinking_exhaustion.py diff --git a/tests/run_agent/test_lmstudio_load_mode.py b/tests/agent/test_lmstudio_load_mode.py similarity index 100% rename from tests/run_agent/test_lmstudio_load_mode.py rename to tests/agent/test_lmstudio_load_mode.py diff --git a/tests/agent/test_load_time_durability_stamp_92231.py b/tests/agent/test_load_time_durability_stamp.py similarity index 100% rename from tests/agent/test_load_time_durability_stamp_92231.py rename to tests/agent/test_load_time_durability_stamp.py diff --git a/tests/run_agent/test_long_context_tier_429.py b/tests/agent/test_long_context_tier_429.py similarity index 100% rename from tests/run_agent/test_long_context_tier_429.py rename to tests/agent/test_long_context_tier_429.py diff --git a/tests/run_agent/test_malformed_tool_arguments.py b/tests/agent/test_malformed_tool_arguments.py similarity index 100% rename from tests/run_agent/test_malformed_tool_arguments.py rename to tests/agent/test_malformed_tool_arguments.py diff --git a/tests/run_agent/test_materialize_data_url_cleanup.py b/tests/agent/test_materialize_data_url_cleanup.py similarity index 100% rename from tests/run_agent/test_materialize_data_url_cleanup.py rename to tests/agent/test_materialize_data_url_cleanup.py diff --git a/tests/run_agent/test_memory_nudge_counter_hydration.py b/tests/agent/test_memory_nudge_counter_hydration.py similarity index 100% rename from tests/run_agent/test_memory_nudge_counter_hydration.py rename to tests/agent/test_memory_nudge_counter_hydration.py diff --git a/tests/run_agent/test_memory_provider_init.py b/tests/agent/test_memory_provider_init.py similarity index 100% rename from tests/run_agent/test_memory_provider_init.py rename to tests/agent/test_memory_provider_init.py diff --git a/tests/run_agent/test_memory_sync_interrupted.py b/tests/agent/test_memory_sync_interrupted.py similarity index 98% rename from tests/run_agent/test_memory_sync_interrupted.py rename to tests/agent/test_memory_sync_interrupted.py index e879676d1f..8cf07a8063 100644 --- a/tests/run_agent/test_memory_sync_interrupted.py +++ b/tests/agent/test_memory_sync_interrupted.py @@ -25,7 +25,7 @@ import pytest def _bare_agent(): """Build an ``AIAgent`` with only the attributes ``_sync_external_memory_for_turn`` touches — matches the bare-agent - pattern used across ``tests/run_agent/test_interrupt_propagation.py``. + pattern used across ``tests/agent/test_interrupt_propagation.py``. """ from run_agent import AIAgent diff --git a/tests/test_message_reactions.py b/tests/agent/test_message_reactions.py similarity index 100% rename from tests/test_message_reactions.py rename to tests/agent/test_message_reactions.py diff --git a/tests/run_agent/test_message_sequence_repair.py b/tests/agent/test_message_sequence_repair.py similarity index 100% rename from tests/run_agent/test_message_sequence_repair.py rename to tests/agent/test_message_sequence_repair.py diff --git a/tests/agent/test_meta_agent_init.py b/tests/agent/test_meta_agent_init.py index 2d56453d10..c916d51385 100644 --- a/tests/agent/test_meta_agent_init.py +++ b/tests/agent/test_meta_agent_init.py @@ -116,7 +116,7 @@ def test_agent_init_anthropic_url_implies_provider_and_api_mode(): def test_agent_init_anthropic_url_preserves_credential_pool(): """Anthropic-scoped credential pool must survive provider auto-detection. - Mirrors tests/run_agent/test_63425_credential_pool_auto_detect.py at the + Mirrors tests/agent/test_credential_pool_auto_detect.py at the public AIAgent surface (provider=None URL detection). """ from types import SimpleNamespace diff --git a/tests/agent/test_moa_cold_start_cache_66793.py b/tests/agent/test_moa_cold_start_cache.py similarity index 100% rename from tests/agent/test_moa_cold_start_cache_66793.py rename to tests/agent/test_moa_cold_start_cache.py diff --git a/tests/run_agent/test_moa_fanout_cadence.py b/tests/agent/test_moa_fanout_cadence.py similarity index 100% rename from tests/run_agent/test_moa_fanout_cadence.py rename to tests/agent/test_moa_fanout_cadence.py diff --git a/tests/run_agent/test_moa_loop_mode.py b/tests/agent/test_moa_loop_mode.py similarity index 100% rename from tests/run_agent/test_moa_loop_mode.py rename to tests/agent/test_moa_loop_mode.py diff --git a/tests/test_moa_prepared_request_leak_78382.py b/tests/agent/test_moa_prepared_request_leak.py similarity index 100% rename from tests/test_moa_prepared_request_leak_78382.py rename to tests/agent/test_moa_prepared_request_leak.py diff --git a/tests/run_agent/test_moa_privacy_filter.py b/tests/agent/test_moa_privacy_filter.py similarity index 100% rename from tests/run_agent/test_moa_privacy_filter.py rename to tests/agent/test_moa_privacy_filter.py diff --git a/tests/run_agent/test_moa_streaming.py b/tests/agent/test_moa_streaming.py similarity index 100% rename from tests/run_agent/test_moa_streaming.py rename to tests/agent/test_moa_streaming.py diff --git a/tests/run_agent/test_model_streaming_config.py b/tests/agent/test_model_streaming_config.py similarity index 100% rename from tests/run_agent/test_model_streaming_config.py rename to tests/agent/test_model_streaming_config.py diff --git a/tests/run_agent/test_66267_multimodal_interim.py b/tests/agent/test_multimodal_interim.py similarity index 100% rename from tests/run_agent/test_66267_multimodal_interim.py rename to tests/agent/test_multimodal_interim.py diff --git a/tests/run_agent/test_multimodal_tool_content_recovery.py b/tests/agent/test_multimodal_tool_content_recovery.py similarity index 100% rename from tests/run_agent/test_multimodal_tool_content_recovery.py rename to tests/agent/test_multimodal_tool_content_recovery.py diff --git a/tests/run_agent/test_native_compaction.py b/tests/agent/test_native_compaction.py similarity index 100% rename from tests/run_agent/test_native_compaction.py rename to tests/agent/test_native_compaction.py diff --git a/tests/run_agent/test_native_compaction_nontext_retention.py b/tests/agent/test_native_compaction_nontext_retention.py similarity index 100% rename from tests/run_agent/test_native_compaction_nontext_retention.py rename to tests/agent/test_native_compaction_nontext_retention.py diff --git a/tests/run_agent/test_native_compaction_summary_retention.py b/tests/agent/test_native_compaction_summary_retention.py similarity index 100% rename from tests/run_agent/test_native_compaction_summary_retention.py rename to tests/agent/test_native_compaction_summary_retention.py diff --git a/tests/run_agent/test_native_compaction_switch_capabilities.py b/tests/agent/test_native_compaction_switch_capabilities.py similarity index 100% rename from tests/run_agent/test_native_compaction_switch_capabilities.py rename to tests/agent/test_native_compaction_switch_capabilities.py diff --git a/tests/run_agent/test_nonretryable_error_html_summary.py b/tests/agent/test_nonretryable_error_html_summary.py similarity index 100% rename from tests/run_agent/test_nonretryable_error_html_summary.py rename to tests/agent/test_nonretryable_error_html_summary.py diff --git a/tests/run_agent/test_notice_spine.py b/tests/agent/test_notice_spine.py similarity index 98% rename from tests/run_agent/test_notice_spine.py rename to tests/agent/test_notice_spine.py index b928e6e9ca..2587cc7331 100644 --- a/tests/run_agent/test_notice_spine.py +++ b/tests/agent/test_notice_spine.py @@ -5,7 +5,7 @@ Covers: object.__new__ — same pattern as test_steer.py and test_file_mutation_verifier.py). B. Constructor / init_agent signature threading. C. TUI _agent_cbs notice binding — mirrors the status_callback tests already - in tests/test_tui_gateway_server.py. + in tests/tui_gateway/test_tui_gateway_server.py. """ from __future__ import annotations diff --git a/tests/run_agent/test_nous_429_fallback_reentry.py b/tests/agent/test_nous_429_fallback_reentry.py similarity index 100% rename from tests/run_agent/test_nous_429_fallback_reentry.py rename to tests/agent/test_nous_429_fallback_reentry.py diff --git a/tests/run_agent/test_nous_fallback_unavailable.py b/tests/agent/test_nous_fallback_unavailable.py similarity index 100% rename from tests/run_agent/test_nous_fallback_unavailable.py rename to tests/agent/test_nous_fallback_unavailable.py diff --git a/tests/test_ollama_num_ctx.py b/tests/agent/test_ollama_num_ctx.py similarity index 100% rename from tests/test_ollama_num_ctx.py rename to tests/agent/test_ollama_num_ctx.py diff --git a/tests/test_onepassword_secrets.py b/tests/agent/test_onepassword_secrets.py similarity index 99% rename from tests/test_onepassword_secrets.py rename to tests/agent/test_onepassword_secrets.py index 8f2da661a4..cafcf60ad3 100644 --- a/tests/test_onepassword_secrets.py +++ b/tests/agent/test_onepassword_secrets.py @@ -19,7 +19,7 @@ import pytest # Make the worktree importable without depending on the installed wheel. -ROOT = Path(__file__).resolve().parents[1] +ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) diff --git a/tests/run_agent/test_openai_client_lifecycle.py b/tests/agent/test_openai_client_lifecycle.py similarity index 100% rename from tests/run_agent/test_openai_client_lifecycle.py rename to tests/agent/test_openai_client_lifecycle.py diff --git a/tests/run_agent/test_opencode_free_client_headers.py b/tests/agent/test_opencode_free_client_headers.py similarity index 100% rename from tests/run_agent/test_opencode_free_client_headers.py rename to tests/agent/test_opencode_free_client_headers.py diff --git a/tests/run_agent/test_92450_outer_error_retry_bound.py b/tests/agent/test_outer_error_retry_bound.py similarity index 100% rename from tests/run_agent/test_92450_outer_error_retry_bound.py rename to tests/agent/test_outer_error_retry_bound.py diff --git a/tests/test_output_cap_parsing.py b/tests/agent/test_output_cap_parsing.py similarity index 100% rename from tests/test_output_cap_parsing.py rename to tests/agent/test_output_cap_parsing.py diff --git a/tests/run_agent/test_overflow_overhead_aware_tokens.py b/tests/agent/test_overflow_overhead_aware_tokens.py similarity index 100% rename from tests/run_agent/test_overflow_overhead_aware_tokens.py rename to tests/agent/test_overflow_overhead_aware_tokens.py diff --git a/tests/run_agent/test_overflow_partial_terminal_106260.py b/tests/agent/test_overflow_partial_terminal.py similarity index 100% rename from tests/run_agent/test_overflow_partial_terminal_106260.py rename to tests/agent/test_overflow_partial_terminal.py diff --git a/tests/run_agent/test_partial_stream_finish_reason.py b/tests/agent/test_partial_stream_finish_reason.py similarity index 100% rename from tests/run_agent/test_partial_stream_finish_reason.py rename to tests/agent/test_partial_stream_finish_reason.py diff --git a/tests/run_agent/test_per_model_compression_threshold.py b/tests/agent/test_per_model_compression_threshold.py similarity index 100% rename from tests/run_agent/test_per_model_compression_threshold.py rename to tests/agent/test_per_model_compression_threshold.py diff --git a/tests/run_agent/test_per_model_threshold_init_ordering.py b/tests/agent/test_per_model_threshold_init_ordering.py similarity index 100% rename from tests/run_agent/test_per_model_threshold_init_ordering.py rename to tests/agent/test_per_model_threshold_init_ordering.py diff --git a/tests/run_agent/test_percentage_clamp.py b/tests/agent/test_percentage_clamp.py similarity index 100% rename from tests/run_agent/test_percentage_clamp.py rename to tests/agent/test_percentage_clamp.py diff --git a/tests/run_agent/test_plugin_context_engine_init.py b/tests/agent/test_plugin_context_engine_init.py similarity index 100% rename from tests/run_agent/test_plugin_context_engine_init.py rename to tests/agent/test_plugin_context_engine_init.py diff --git a/tests/run_agent/test_plugin_stream_hooks.py b/tests/agent/test_plugin_stream_hooks.py similarity index 100% rename from tests/run_agent/test_plugin_stream_hooks.py rename to tests/agent/test_plugin_stream_hooks.py diff --git a/tests/run_agent/test_post_tool_compression_attempt_cap.py b/tests/agent/test_post_tool_compression_attempt_cap.py similarity index 100% rename from tests/run_agent/test_post_tool_compression_attempt_cap.py rename to tests/agent/test_post_tool_compression_attempt_cap.py diff --git a/tests/run_agent/test_pre_compress_memory_context.py b/tests/agent/test_pre_compress_memory_context_handoff.py similarity index 100% rename from tests/run_agent/test_pre_compress_memory_context.py rename to tests/agent/test_pre_compress_memory_context_handoff.py diff --git a/tests/run_agent/test_preflight_compression_cap_e2e.py b/tests/agent/test_preflight_compression_cap_e2e.py similarity index 100% rename from tests/run_agent/test_preflight_compression_cap_e2e.py rename to tests/agent/test_preflight_compression_cap_e2e.py diff --git a/tests/agent/test_preflight_lock_defer.py b/tests/agent/test_preflight_lock_defer.py index 4a0e9e6b11..006ba81145 100644 --- a/tests/agent/test_preflight_lock_defer.py +++ b/tests/agent/test_preflight_lock_defer.py @@ -1,6 +1,6 @@ """Preflight lock-defer must not arm the insufficient-progress blocker. -Companion to ``tests/run_agent/test_compression_lock_defer.py`` — pins the +Companion to ``tests/agent/test_compression_lock_defer.py`` — pins the ``build_turn_context`` preflight loop's handling of a lock-contended compression no-op (#69870 lock-skip signal, consumer salvaged from #49874): diff --git a/tests/run_agent/test_preflight_timeout_typed_result.py b/tests/agent/test_preflight_timeout_typed_result.py similarity index 100% rename from tests/run_agent/test_preflight_timeout_typed_result.py rename to tests/agent/test_preflight_timeout_typed_result.py diff --git a/tests/run_agent/test_primary_runtime_restore.py b/tests/agent/test_primary_runtime_restore.py similarity index 100% rename from tests/run_agent/test_primary_runtime_restore.py rename to tests/agent/test_primary_runtime_restore.py diff --git a/tests/test_private_credential_writers.py b/tests/agent/test_private_credential_writers.py similarity index 100% rename from tests/test_private_credential_writers.py rename to tests/agent/test_private_credential_writers.py diff --git a/tests/run_agent/test_proactive_prune_loop_wiring.py b/tests/agent/test_proactive_prune_loop_wiring.py similarity index 100% rename from tests/run_agent/test_proactive_prune_loop_wiring.py rename to tests/agent/test_proactive_prune_loop_wiring.py diff --git a/tests/agent/test_protected_tail_pressure_61932.py b/tests/agent/test_protected_tail_pressure.py similarity index 100% rename from tests/agent/test_protected_tail_pressure_61932.py rename to tests/agent/test_protected_tail_pressure.py diff --git a/tests/run_agent/test_provider_attribution_headers.py b/tests/agent/test_provider_attribution_headers.py similarity index 100% rename from tests/run_agent/test_provider_attribution_headers.py rename to tests/agent/test_provider_attribution_headers.py diff --git a/tests/run_agent/test_provider_fallback.py b/tests/agent/test_provider_fallback.py similarity index 100% rename from tests/run_agent/test_provider_fallback.py rename to tests/agent/test_provider_fallback.py diff --git a/tests/run_agent/test_provider_parity.py b/tests/agent/test_provider_parity.py similarity index 100% rename from tests/run_agent/test_provider_parity.py rename to tests/agent/test_provider_parity.py diff --git a/tests/run_agent/test_reasoning_echo_resolver_e2e.py b/tests/agent/test_reasoning_echo_resolver_e2e.py similarity index 100% rename from tests/run_agent/test_reasoning_echo_resolver_e2e.py rename to tests/agent/test_reasoning_echo_resolver_e2e.py diff --git a/tests/test_redaction_registry.py b/tests/agent/test_redaction_registry.py similarity index 100% rename from tests/test_redaction_registry.py rename to tests/agent/test_redaction_registry.py diff --git a/tests/run_agent/test_repair_reanchors_current_turn_user_idx.py b/tests/agent/test_repair_reanchors_current_turn_user_idx.py similarity index 100% rename from tests/run_agent/test_repair_reanchors_current_turn_user_idx.py rename to tests/agent/test_repair_reanchors_current_turn_user_idx.py diff --git a/tests/run_agent/test_repair_tool_call_arguments.py b/tests/agent/test_repair_tool_call_arguments.py similarity index 100% rename from tests/run_agent/test_repair_tool_call_arguments.py rename to tests/agent/test_repair_tool_call_arguments.py diff --git a/tests/run_agent/test_repair_tool_call_name.py b/tests/agent/test_repair_tool_call_name.py similarity index 100% rename from tests/run_agent/test_repair_tool_call_name.py rename to tests/agent/test_repair_tool_call_name.py diff --git a/tests/run_agent/test_request_client_reuse_abort_races.py b/tests/agent/test_request_client_reuse_abort_races.py similarity index 100% rename from tests/run_agent/test_request_client_reuse_abort_races.py rename to tests/agent/test_request_client_reuse_abort_races.py diff --git a/tests/run_agent/test_reset_aware_primary_restore.py b/tests/agent/test_reset_aware_primary_restore.py similarity index 100% rename from tests/run_agent/test_reset_aware_primary_restore.py rename to tests/agent/test_reset_aware_primary_restore.py diff --git a/tests/run_agent/test_retry_status_buffer.py b/tests/agent/test_retry_status_buffer.py similarity index 100% rename from tests/run_agent/test_retry_status_buffer.py rename to tests/agent/test_retry_status_buffer.py diff --git a/tests/test_retry_utils.py b/tests/agent/test_retry_utils.py similarity index 100% rename from tests/test_retry_utils.py rename to tests/agent/test_retry_utils.py diff --git a/tests/run_agent/test_review_prompt_class_first.py b/tests/agent/test_review_prompt_class_first.py similarity index 100% rename from tests/run_agent/test_review_prompt_class_first.py rename to tests/agent/test_review_prompt_class_first.py diff --git a/tests/agent/test_rotation_flush_persisted_boundary_68196.py b/tests/agent/test_rotation_flush_persisted_boundary.py similarity index 100% rename from tests/agent/test_rotation_flush_persisted_boundary_68196.py rename to tests/agent/test_rotation_flush_persisted_boundary.py diff --git a/tests/run_agent/test_run_agent.py b/tests/agent/test_run_agent.py similarity index 100% rename from tests/run_agent/test_run_agent.py rename to tests/agent/test_run_agent.py diff --git a/tests/run_agent/test_run_agent_codex_responses.py b/tests/agent/test_run_agent_codex_responses.py similarity index 100% rename from tests/run_agent/test_run_agent_codex_responses.py rename to tests/agent/test_run_agent_codex_responses.py diff --git a/tests/run_agent/test_run_agent_multimodal_prologue.py b/tests/agent/test_run_agent_multimodal_prologue.py similarity index 100% rename from tests/run_agent/test_run_agent_multimodal_prologue.py rename to tests/agent/test_run_agent_multimodal_prologue.py diff --git a/tests/run_agent/test_sanitiser_escalation.py b/tests/agent/test_sanitiser_escalation.py similarity index 100% rename from tests/run_agent/test_sanitiser_escalation.py rename to tests/agent/test_sanitiser_escalation.py diff --git a/tests/run_agent/test_sequential_chats_live.py b/tests/agent/test_sequential_chats_live.py similarity index 98% rename from tests/run_agent/test_sequential_chats_live.py rename to tests/agent/test_sequential_chats_live.py index f6b9937bda..9a32c15cd9 100644 --- a/tests/run_agent/test_sequential_chats_live.py +++ b/tests/agent/test_sequential_chats_live.py @@ -11,7 +11,7 @@ reproduction against a real provider so we have a true end-to-end smoke test for any future keepalive / transport plumbing. Opt-in — not part of default CI: - HERMES_LIVE_TESTS=1 pytest tests/run_agent/test_sequential_chats_live.py -v + HERMES_LIVE_TESTS=1 pytest tests/agent/test_sequential_chats_live.py -v Requires ``OPENROUTER_API_KEY`` to be set (or sourced via ~/.hermes/.env). """ diff --git a/tests/run_agent/test_sequential_tool_timeout.py b/tests/agent/test_sequential_tool_timeout.py similarity index 100% rename from tests/run_agent/test_sequential_tool_timeout.py rename to tests/agent/test_sequential_tool_timeout.py diff --git a/tests/run_agent/test_session_activity_persist.py b/tests/agent/test_session_activity_persist.py similarity index 100% rename from tests/run_agent/test_session_activity_persist.py rename to tests/agent/test_session_activity_persist.py diff --git a/tests/run_agent/test_session_id_env.py b/tests/agent/test_session_id_env.py similarity index 100% rename from tests/run_agent/test_session_id_env.py rename to tests/agent/test_session_id_env.py diff --git a/tests/run_agent/test_session_meta_filtering.py b/tests/agent/test_session_meta_filtering.py similarity index 100% rename from tests/run_agent/test_session_meta_filtering.py rename to tests/agent/test_session_meta_filtering.py diff --git a/tests/run_agent/test_session_reset_fix.py b/tests/agent/test_session_reset_fix.py similarity index 100% rename from tests/run_agent/test_session_reset_fix.py rename to tests/agent/test_session_reset_fix.py diff --git a/tests/agent/test_session_rotation_flush_cold_resume_68454.py b/tests/agent/test_session_rotation_flush_cold_resume.py similarity index 100% rename from tests/agent/test_session_rotation_flush_cold_resume_68454.py rename to tests/agent/test_session_rotation_flush_cold_resume.py diff --git a/tests/run_agent/test_session_source.py b/tests/agent/test_session_source.py similarity index 100% rename from tests/run_agent/test_session_source.py rename to tests/agent/test_session_source.py diff --git a/tests/run_agent/test_70773_shared_client_fd_corruption.py b/tests/agent/test_shared_client_fd_corruption.py similarity index 100% rename from tests/run_agent/test_70773_shared_client_fd_corruption.py rename to tests/agent/test_shared_client_fd_corruption.py diff --git a/tests/run_agent/test_skill_applied_notifications.py b/tests/agent/test_skill_applied_notifications.py similarity index 100% rename from tests/run_agent/test_skill_applied_notifications.py rename to tests/agent/test_skill_applied_notifications.py diff --git a/tests/agent/test_skip_memory_store_65429.py b/tests/agent/test_skip_memory_store.py similarity index 100% rename from tests/agent/test_skip_memory_store_65429.py rename to tests/agent/test_skip_memory_store.py diff --git a/tests/test_sql_injection.py b/tests/agent/test_sql_injection.py similarity index 100% rename from tests/test_sql_injection.py rename to tests/agent/test_sql_injection.py diff --git a/tests/run_agent/test_start_order_gate.py b/tests/agent/test_start_order_gate.py similarity index 100% rename from tests/run_agent/test_start_order_gate.py rename to tests/agent/test_start_order_gate.py diff --git a/tests/run_agent/test_steer.py b/tests/agent/test_steer.py similarity index 100% rename from tests/run_agent/test_steer.py rename to tests/agent/test_steer.py diff --git a/tests/run_agent/test_stream_drop_logging.py b/tests/agent/test_stream_drop_logging.py similarity index 100% rename from tests/run_agent/test_stream_drop_logging.py rename to tests/agent/test_stream_drop_logging.py diff --git a/tests/run_agent/test_stream_interrupt_retry.py b/tests/agent/test_stream_interrupt_retry.py similarity index 100% rename from tests/run_agent/test_stream_interrupt_retry.py rename to tests/agent/test_stream_interrupt_retry.py diff --git a/tests/run_agent/test_stream_single_writer_65991.py b/tests/agent/test_stream_single_writer.py similarity index 100% rename from tests/run_agent/test_stream_single_writer_65991.py rename to tests/agent/test_stream_single_writer.py diff --git a/tests/run_agent/test_stream_stale_breaker_reset.py b/tests/agent/test_stream_stale_breaker_reset.py similarity index 98% rename from tests/run_agent/test_stream_stale_breaker_reset.py rename to tests/agent/test_stream_stale_breaker_reset.py index 919b9ea865..297a1cd7f4 100644 --- a/tests/run_agent/test_stream_stale_breaker_reset.py +++ b/tests/agent/test_stream_stale_breaker_reset.py @@ -27,7 +27,7 @@ from run_agent import AIAgent def _make_agent_openrouter(): """Minimal openrouter agent (skips __init__), mirroring - tests/run_agent/test_switch_model_rollback.py.""" + tests/agent/test_switch_model_rollback.py.""" agent = AIAgent.__new__(AIAgent) agent.provider = "openrouter" @@ -58,7 +58,7 @@ def _make_agent_openrouter(): def _make_fallback_agent(fallback_model): """Full-constructor agent for the fallback path, mirroring - tests/run_agent/test_24996_fallback_exhaustion_cooldown.py.""" + tests/agent/test_fallback_exhaustion_cooldown.py.""" with ( patch("model_tools.get_tool_definitions", return_value=[]), patch("model_tools.check_toolset_requirements", return_value={}), diff --git a/tests/run_agent/test_stream_stale_circuit_breaker.py b/tests/agent/test_stream_stale_circuit_breaker.py similarity index 98% rename from tests/run_agent/test_stream_stale_circuit_breaker.py rename to tests/agent/test_stream_stale_circuit_breaker.py index af6e6c26d6..a2bddeaa33 100644 --- a/tests/run_agent/test_stream_stale_circuit_breaker.py +++ b/tests/agent/test_stream_stale_circuit_breaker.py @@ -11,7 +11,7 @@ These tests cover the guard added to ``interruptible_streaming_api_call``: - a successful stream resets the consecutive-stale streak; - a stale-stream kill increments the consecutive-stale streak. -The harness mirrors tests/run_agent/test_28161_anthropic_stream_pool_cleanup.py. +The harness mirrors tests/agent/test_anthropic_stream_pool_cleanup.py. """ import threading diff --git a/tests/run_agent/test_streamed_text_accumulation.py b/tests/agent/test_streamed_text_accumulation.py similarity index 100% rename from tests/run_agent/test_streamed_text_accumulation.py rename to tests/agent/test_streamed_text_accumulation.py diff --git a/tests/run_agent/test_streaming.py b/tests/agent/test_streaming.py similarity index 100% rename from tests/run_agent/test_streaming.py rename to tests/agent/test_streaming.py diff --git a/tests/run_agent/test_streaming_tool_call_repair.py b/tests/agent/test_streaming_tool_call_repair.py similarity index 100% rename from tests/run_agent/test_streaming_tool_call_repair.py rename to tests/agent/test_streaming_tool_call_repair.py diff --git a/tests/run_agent/test_strict_api_validation.py b/tests/agent/test_strict_api_validation.py similarity index 100% rename from tests/run_agent/test_strict_api_validation.py rename to tests/agent/test_strict_api_validation.py diff --git a/tests/run_agent/test_strip_reasoning_tags_cli.py b/tests/agent/test_strip_reasoning_tags_cli.py similarity index 100% rename from tests/run_agent/test_strip_reasoning_tags_cli.py rename to tests/agent/test_strip_reasoning_tags_cli.py diff --git a/tests/run_agent/test_summarize_api_error.py b/tests/agent/test_summarize_api_error.py similarity index 100% rename from tests/run_agent/test_summarize_api_error.py rename to tests/agent/test_summarize_api_error.py diff --git a/tests/run_agent/test_switch_model_bedrock_sigv4.py b/tests/agent/test_switch_model_bedrock_sigv4.py similarity index 100% rename from tests/run_agent/test_switch_model_bedrock_sigv4.py rename to tests/agent/test_switch_model_bedrock_sigv4.py diff --git a/tests/run_agent/test_switch_model_context.py b/tests/agent/test_switch_model_context.py similarity index 100% rename from tests/run_agent/test_switch_model_context.py rename to tests/agent/test_switch_model_context.py diff --git a/tests/run_agent/test_switch_model_fallback_prune.py b/tests/agent/test_switch_model_fallback_prune.py similarity index 100% rename from tests/run_agent/test_switch_model_fallback_prune.py rename to tests/agent/test_switch_model_fallback_prune.py diff --git a/tests/run_agent/test_switch_model_pool_reload_52727.py b/tests/agent/test_switch_model_pool_reload.py similarity index 100% rename from tests/run_agent/test_switch_model_pool_reload_52727.py rename to tests/agent/test_switch_model_pool_reload.py diff --git a/tests/run_agent/test_switch_model_reapplies_headers.py b/tests/agent/test_switch_model_reapplies_headers.py similarity index 100% rename from tests/run_agent/test_switch_model_reapplies_headers.py rename to tests/agent/test_switch_model_reapplies_headers.py diff --git a/tests/run_agent/test_switch_model_reasoning_override.py b/tests/agent/test_switch_model_reasoning_override.py similarity index 100% rename from tests/run_agent/test_switch_model_reasoning_override.py rename to tests/agent/test_switch_model_reasoning_override.py diff --git a/tests/run_agent/test_switch_model_rollback.py b/tests/agent/test_switch_model_rollback.py similarity index 100% rename from tests/run_agent/test_switch_model_rollback.py rename to tests/agent/test_switch_model_rollback.py diff --git a/tests/run_agent/test_switch_model_stale_base_url.py b/tests/agent/test_switch_model_stale_base_url.py similarity index 100% rename from tests/run_agent/test_switch_model_stale_base_url.py rename to tests/agent/test_switch_model_stale_base_url.py diff --git a/tests/run_agent/test_81641_text_turn_incremental_persistence.py b/tests/agent/test_text_turn_incremental_persistence.py similarity index 100% rename from tests/run_agent/test_81641_text_turn_incremental_persistence.py rename to tests/agent/test_text_turn_incremental_persistence.py diff --git a/tests/run_agent/test_thinking_only_sanitizer.py b/tests/agent/test_thinking_only_sanitizer.py similarity index 100% rename from tests/run_agent/test_thinking_only_sanitizer.py rename to tests/agent/test_thinking_only_sanitizer.py diff --git a/tests/run_agent/test_thinking_prefill_trailing_turn.py b/tests/agent/test_thinking_prefill_trailing_turn.py similarity index 100% rename from tests/run_agent/test_thinking_prefill_trailing_turn.py rename to tests/agent/test_thinking_prefill_trailing_turn.py diff --git a/tests/run_agent/test_thinking_sig_recovery_persistence.py b/tests/agent/test_thinking_sig_recovery_persistence.py similarity index 100% rename from tests/run_agent/test_thinking_sig_recovery_persistence.py rename to tests/agent/test_thinking_sig_recovery_persistence.py diff --git a/tests/run_agent/test_tls_fd_recycle_corruption.py b/tests/agent/test_tls_fd_recycle_corruption.py similarity index 100% rename from tests/run_agent/test_tls_fd_recycle_corruption.py rename to tests/agent/test_tls_fd_recycle_corruption.py diff --git a/tests/run_agent/test_token_persistence_non_cli.py b/tests/agent/test_token_persistence_non_cli.py similarity index 100% rename from tests/run_agent/test_token_persistence_non_cli.py rename to tests/agent/test_token_persistence_non_cli.py diff --git a/tests/run_agent/test_tool_activity_heartbeat.py b/tests/agent/test_tool_activity_heartbeat.py similarity index 100% rename from tests/run_agent/test_tool_activity_heartbeat.py rename to tests/agent/test_tool_activity_heartbeat.py diff --git a/tests/run_agent/test_tool_arg_coercion.py b/tests/agent/test_tool_arg_coercion.py similarity index 100% rename from tests/run_agent/test_tool_arg_coercion.py rename to tests/agent/test_tool_arg_coercion.py diff --git a/tests/run_agent/test_tool_batch_segmentation.py b/tests/agent/test_tool_batch_segmentation.py similarity index 100% rename from tests/run_agent/test_tool_batch_segmentation.py rename to tests/agent/test_tool_batch_segmentation.py diff --git a/tests/run_agent/test_tool_call_args_sanitizer.py b/tests/agent/test_tool_call_args_sanitizer.py similarity index 100% rename from tests/run_agent/test_tool_call_args_sanitizer.py rename to tests/agent/test_tool_call_args_sanitizer.py diff --git a/tests/run_agent/test_tool_call_guardrail_runtime.py b/tests/agent/test_tool_call_guardrail_runtime.py similarity index 100% rename from tests/run_agent/test_tool_call_guardrail_runtime.py rename to tests/agent/test_tool_call_guardrail_runtime.py diff --git a/tests/run_agent/test_tool_call_incremental_persistence.py b/tests/agent/test_tool_call_incremental_persistence.py similarity index 100% rename from tests/run_agent/test_tool_call_incremental_persistence.py rename to tests/agent/test_tool_call_incremental_persistence.py diff --git a/tests/run_agent/test_tool_executor_contextvar_propagation.py b/tests/agent/test_tool_executor_contextvar_propagation.py similarity index 100% rename from tests/run_agent/test_tool_executor_contextvar_propagation.py rename to tests/agent/test_tool_executor_contextvar_propagation.py diff --git a/tests/run_agent/test_tool_name_db_persistence.py b/tests/agent/test_tool_name_db_persistence.py similarity index 100% rename from tests/run_agent/test_tool_name_db_persistence.py rename to tests/agent/test_tool_name_db_persistence.py diff --git a/tests/run_agent/test_turn_completion_explainer.py b/tests/agent/test_turn_completion_explainer.py similarity index 99% rename from tests/run_agent/test_turn_completion_explainer.py rename to tests/agent/test_turn_completion_explainer.py index 6137423291..160bc18dc0 100644 --- a/tests/run_agent/test_turn_completion_explainer.py +++ b/tests/agent/test_turn_completion_explainer.py @@ -27,7 +27,7 @@ from run_agent import AIAgent # -------------------------------------------------------------------------- -# Fixtures (mirrors tests/run_agent/test_tool_call_guardrail_runtime.py) +# Fixtures (mirrors tests/agent/test_tool_call_guardrail_runtime.py) # -------------------------------------------------------------------------- def _mock_response(content="Hello", finish_reason="stop", tool_calls=None): msg = SimpleNamespace(content=content, tool_calls=tool_calls) diff --git a/tests/run_agent/test_turn_liveness_watchdog.py b/tests/agent/test_turn_liveness_watchdog.py similarity index 100% rename from tests/run_agent/test_turn_liveness_watchdog.py rename to tests/agent/test_turn_liveness_watchdog.py diff --git a/tests/run_agent/test_unicode_ascii_codec.py b/tests/agent/test_unicode_ascii_codec.py similarity index 100% rename from tests/run_agent/test_unicode_ascii_codec.py rename to tests/agent/test_unicode_ascii_codec.py diff --git a/tests/run_agent/test_verification_continuation_budget.py b/tests/agent/test_verification_continuation_budget.py similarity index 100% rename from tests/run_agent/test_verification_continuation_budget.py rename to tests/agent/test_verification_continuation_budget.py diff --git a/tests/run_agent/test_vision_aware_preprocessing.py b/tests/agent/test_vision_aware_preprocessing.py similarity index 100% rename from tests/run_agent/test_vision_aware_preprocessing.py rename to tests/agent/test_vision_aware_preprocessing.py diff --git a/tests/agent/test_vision_routing_31179.py b/tests/agent/test_vision_routing.py similarity index 100% rename from tests/agent/test_vision_routing_31179.py rename to tests/agent/test_vision_routing.py diff --git a/tests/run_agent/test_vision_tool_messages.py b/tests/agent/test_vision_tool_messages.py similarity index 100% rename from tests/run_agent/test_vision_tool_messages.py rename to tests/agent/test_vision_tool_messages.py diff --git a/tests/run_agent/test_wait_state_visibility.py b/tests/agent/test_wait_state_visibility.py similarity index 100% rename from tests/run_agent/test_wait_state_visibility.py rename to tests/agent/test_wait_state_visibility.py diff --git a/tests/ci/test_classify_changes.py b/tests/ci/test_classify_changes.py index c3c12e9c9e..e5f312c18c 100644 --- a/tests/ci/test_classify_changes.py +++ b/tests/ci/test_classify_changes.py @@ -170,7 +170,7 @@ CASES = { _lanes(python=True, frontend=True, desktop_updater=True), ), "desktop-update test → desktop_updater": ( - ["tests/test_desktop_update_windows_progress.py"], + ["tests/scripts/desktop_update/test_desktop_update_windows_progress.py"], _lanes(python=True, python_prod=False, scan=True, desktop_updater=True), ), "updater-process.ts → desktop_updater": ( @@ -380,14 +380,14 @@ def test_pull_request_changed_files_parses_gh_output(tmp_path, monkeypatch): return subprocess.CompletedProcess( args[0], 0, - stdout="scripts/install.sh\ntests/test_install_sh_node_deps_workspaces.py\n", + stdout="scripts/install.sh\ntests/scripts/install/test_install_sh_node_deps_workspaces.py\n", stderr="", ) monkeypatch.setattr(_mod.subprocess, "run", fake_run) assert pull_request_changed_files() == [ "scripts/install.sh", - "tests/test_install_sh_node_deps_workspaces.py", + "tests/scripts/install/test_install_sh_node_deps_workspaces.py", ] @@ -408,7 +408,7 @@ def test_main_recovers_pr_files_instead_of_fail_open_ci_review(monkeypatch, caps monkeypatch.setattr( _mod, "pull_request_changed_files", - lambda: ["scripts/install.sh", "tests/test_install_sh_node_deps_workspaces.py"], + lambda: ["scripts/install.sh", "tests/scripts/install/test_install_sh_node_deps_workspaces.py"], ) monkeypatch.setattr(sys, "stdin", io.StringIO("\n")) monkeypatch.delenv("GITHUB_OUTPUT", raising=False) diff --git a/tests/cli/__init__.py b/tests/cli/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/cli/conftest.py b/tests/cli/conftest.py deleted file mode 100644 index f3afb04276..0000000000 --- a/tests/cli/conftest.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Shared fixtures for CLI tests. - -prompt_toolkit / capsys isolation ---------------------------------- -``cli._cprint`` renders through ``prompt_toolkit.print_formatted_text``, -which — when called with no explicit ``output=`` — lazily creates an -``Output`` from ``sys.stdout`` **and caches it on the process-global default -``AppSession``** (``prompt_toolkit.application.current._current_app_session``, -a ``ContextVar`` with a module-level default). The cache is keyed to nothing -and never re-reads ``sys.stdout``. - -Under pytest, ``capsys`` swaps ``sys.stdout`` for a fresh buffer per test. -So the first CLI test that emits through ``_cprint`` (e.g. one exercising -``/queue``, which prints a "Queued: …" line) locks prompt_toolkit's cached -output onto *its* captured stdout. Every later ``capsys`` test that asserts -on ``_cprint`` output then reads an empty buffer, because the render went to -the first test's now-dead capture target. That is the mechanism behind the -order-dependent ``test_resume_quiet_stderr`` failure: it passes in isolation -and in its own file, but fails in a full ``tests/cli`` run. - -Reset the cached output before every CLI test so each one re-creates a fresh -prompt_toolkit ``Output`` bound to its own ``sys.stdout`` on first use. This -is a no-op when prompt_toolkit isn't importable and cheap otherwise (the -property re-creates lazily). -""" - -import pytest - - -@pytest.fixture(autouse=True) -def _reset_prompt_toolkit_output_cache(): - """Clear prompt_toolkit's cached AppSession output around each CLI test. - - See the module docstring for the capsys/prompt_toolkit interaction this - guards against. - """ - - def _clear() -> None: - try: - from prompt_toolkit.application.current import get_app_session - - get_app_session()._output = None - except Exception: - # prompt_toolkit not importable / internal shape changed — the - # tests that rely on this simply keep their prior behavior. - pass - - _clear() - yield - _clear() diff --git a/tests/cli/test_cli_skin_integration.py b/tests/cli/test_cli_skin_integration.py deleted file mode 100644 index 4fa73c10e9..0000000000 --- a/tests/cli/test_cli_skin_integration.py +++ /dev/null @@ -1,89 +0,0 @@ -from types import SimpleNamespace -from unittest.mock import MagicMock, patch - -from cli import HermesCLI, _rich_text_from_ansi -from hermes_cli.skin_engine import get_active_skin, set_active_skin - - -def _make_cli_stub(): - cli = HermesCLI.__new__(HermesCLI) - cli._sudo_state = None - cli._secret_state = None - cli._approval_state = None - cli._clarify_state = None - cli._clarify_freetext = False - cli._command_running = False - cli._agent_running = False - cli._voice_recording = False - cli._voice_processing = False - cli._voice_mode = False - cli._command_spinner_frame = lambda: "⟳" - cli._tui_style_base = { - "prompt": "#fff", - "input-area": "#fff", - "input-rule": "#aaa", - "prompt-working": "#888 italic", - } - cli._app = SimpleNamespace(style=None) - cli._invalidate = MagicMock() - return cli - - -class TestCliSkinPromptIntegration: - - def test_ares_prompt_fragments_use_skin_symbol(self): - cli = _make_cli_stub() - - set_active_skin("ares") - assert cli._get_tui_prompt_fragments() == [("class:prompt", "⚔ ")] - - def test_secret_prompt_fragments_preserve_secret_state(self): - cli = _make_cli_stub() - cli._secret_state = {"response_queue": object()} - - set_active_skin("ares") - assert cli._get_tui_prompt_fragments() == [("class:sudo-prompt", "🔑 ⚔ ")] - - - def test_narrow_terminals_compact_voice_recording_prompt_fragments(self): - cli = _make_cli_stub() - cli._voice_recording = True - cli._voice_recorder = SimpleNamespace(current_rms=3000) - - with patch.object(HermesCLI, "_get_tui_terminal_width", return_value=50): - frags = cli._get_tui_prompt_fragments() - - assert frags[0][0] == "class:voice-recording" - assert frags[0][1].startswith("●") - assert "❯" not in frags[0][1] - - - - def test_apply_tui_skin_style_updates_running_app(self): - cli = _make_cli_stub() - - set_active_skin("ares") - assert cli._apply_tui_skin_style() is True - assert cli._app.style is not None - cli._invalidate.assert_called_once_with(min_interval=0.0) - - def test_handle_skin_command_refreshes_live_tui(self, capsys): - cli = _make_cli_stub() - - with patch("cli.save_config_value", return_value=True): - cli._handle_skin_command("/skin ares") - - output = capsys.readouterr().out - assert "Skin set to: ares (saved)" in output - assert "Prompt + TUI colors updated." in output - assert cli._app.style is not None - - -class TestAnsiRichTextHelper: - def test_preserves_literal_brackets(self): - text = _rich_text_from_ansi("[notatag] literal") - assert text.plain == "[notatag] literal" - - def test_strips_ansi_but_keeps_plain_text(self): - text = _rich_text_from_ansi("\x1b[31mred\x1b[0m") - assert text.plain == "red" diff --git a/tests/conftest.py b/tests/conftest.py index f69c015404..45d67cf7a9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -820,7 +820,7 @@ def _state_db_write_guard(request, monkeypatch): # ``_methods`` dict at import time and keeps per-session state in module # globals (sessions, child-run registry, config cache, DB handle). The # canonical per-file process isolation above hides any leakage, but a direct -# multi-file invocation (``pytest tests/tui_gateway/ tests/test_tui_gateway_server.py``, +# multi-file invocation (``pytest tests/tui_gateway/ tests/tui_gateway/test_tui_gateway_server.py``, # or plain ``pytest tests/``) shares one interpreter: a test that stubs # ``_methods["slash.exec"]`` or leaves an active-session lease behind breaks # unrelated tests in later files. This fixture snapshots the cheap-to-copy @@ -1064,7 +1064,7 @@ def _wal_is_usable() -> bool: # Same class of incident as the live-system guard above, different primitive: # a test run spoke the string "partial answer complete" out of the developer's # speakers. That string is a test fixture -# (``tests/test_tui_gateway_server.py``'s fake ``final_response``), and the +# (``tests/tui_gateway/test_tui_gateway_server.py``'s fake ``final_response``), and the # route it took is fully in-process — no leaked shell variable required: # # 1. ``test_voice_toggle_tts_branch_also_carries_record_key`` drives the diff --git a/tests/cron/test_cron_direct_api_call_62151.py b/tests/cron/test_cron_direct_api_call.py similarity index 100% rename from tests/cron/test_cron_direct_api_call_62151.py rename to tests/cron/test_cron_direct_api_call.py diff --git a/tests/cron/test_cron_emfile_stall_87644.py b/tests/cron/test_cron_emfile_stall.py similarity index 100% rename from tests/cron/test_cron_emfile_stall_87644.py rename to tests/cron/test_cron_emfile_stall.py diff --git a/tests/test_cron_manage_profile_scope.py b/tests/cron/test_cron_manage_profile_scope.py similarity index 100% rename from tests/test_cron_manage_profile_scope.py rename to tests/cron/test_cron_manage_profile_scope.py diff --git a/tests/cron/test_cron_run_stale_claim_reap_86721.py b/tests/cron/test_cron_run_stale_claim_reap.py similarity index 100% rename from tests/cron/test_cron_run_stale_claim_reap_86721.py rename to tests/cron/test_cron_run_stale_claim_reap.py diff --git a/tests/cron/test_87033_cronjob_gateway_liveness.py b/tests/cron/test_cronjob_gateway_liveness.py similarity index 100% rename from tests/cron/test_87033_cronjob_gateway_liveness.py rename to tests/cron/test_cronjob_gateway_liveness.py diff --git a/tests/test_estop.py b/tests/cron/test_estop.py similarity index 100% rename from tests/test_estop.py rename to tests/cron/test_estop.py diff --git a/tests/cron/test_jobs_shrink_merge_80624.py b/tests/cron/test_jobs_shrink_merge.py similarity index 100% rename from tests/cron/test_jobs_shrink_merge_80624.py rename to tests/cron/test_jobs_shrink_merge.py diff --git a/tests/cron/test_ticker_stall_60703.py b/tests/cron/test_ticker_stall.py similarity index 100% rename from tests/cron/test_ticker_stall_60703.py rename to tests/cron/test_ticker_stall.py diff --git a/tests/test_timezone.py b/tests/cron/test_timezone.py similarity index 100% rename from tests/test_timezone.py rename to tests/cron/test_timezone.py diff --git a/tests/relay/test_relay_format_hints.py b/tests/gateway/relay/test_relay_format_hints.py similarity index 100% rename from tests/relay/test_relay_format_hints.py rename to tests/gateway/relay/test_relay_format_hints.py diff --git a/tests/relay/test_relay_inchannel_continuable.py b/tests/gateway/relay/test_relay_inchannel_continuable.py similarity index 100% rename from tests/relay/test_relay_inchannel_continuable.py rename to tests/gateway/relay/test_relay_inchannel_continuable.py diff --git a/tests/relay/test_relay_prompt_ack_stream_isolation.py b/tests/gateway/relay/test_relay_prompt_ack_stream_isolation.py similarity index 100% rename from tests/relay/test_relay_prompt_ack_stream_isolation.py rename to tests/gateway/relay/test_relay_prompt_ack_stream_isolation.py diff --git a/tests/gateway/test_35809_auto_reset_clean_context.py b/tests/gateway/test_auto_reset_clean_context.py similarity index 100% rename from tests/gateway/test_35809_auto_reset_clean_context.py rename to tests/gateway/test_auto_reset_clean_context.py diff --git a/tests/gateway/test_10710_auto_reset_evicts_cached_agent.py b/tests/gateway/test_auto_reset_evicts_cached_agent.py similarity index 100% rename from tests/gateway/test_10710_auto_reset_evicts_cached_agent.py rename to tests/gateway/test_auto_reset_evicts_cached_agent.py diff --git a/tests/gateway/test_53175_cleanup_off_loop.py b/tests/gateway/test_cleanup_off_loop.py similarity index 100% rename from tests/gateway/test_53175_cleanup_off_loop.py rename to tests/gateway/test_cleanup_off_loop.py diff --git a/tests/gateway/test_compression_interrupt_demotion_56391.py b/tests/gateway/test_compression_interrupt_demotion.py similarity index 100% rename from tests/gateway/test_compression_interrupt_demotion_56391.py rename to tests/gateway/test_compression_interrupt_demotion.py diff --git a/tests/gateway/test_42039_duplicate_user_message.py b/tests/gateway/test_duplicate_user_message.py similarity index 100% rename from tests/gateway/test_42039_duplicate_user_message.py rename to tests/gateway/test_duplicate_user_message.py diff --git a/tests/test_empty_model_fallback.py b/tests/gateway/test_empty_model_fallback.py similarity index 100% rename from tests/test_empty_model_fallback.py rename to tests/gateway/test_empty_model_fallback.py diff --git a/tests/gateway/test_env_override_explicit_disable_48820.py b/tests/gateway/test_env_override_explicit_disable.py similarity index 100% rename from tests/gateway/test_env_override_explicit_disable_48820.py rename to tests/gateway/test_env_override_explicit_disable.py diff --git a/tests/gateway/test_71671_faulthandler_no_stderr.py b/tests/gateway/test_faulthandler_no_stderr.py similarity index 100% rename from tests/gateway/test_71671_faulthandler_no_stderr.py rename to tests/gateway/test_faulthandler_no_stderr.py diff --git a/tests/test_gateway_streaming_nested_config.py b/tests/gateway/test_gateway_streaming_nested_config.py similarity index 100% rename from tests/test_gateway_streaming_nested_config.py rename to tests/gateway/test_gateway_streaming_nested_config.py diff --git a/tests/gateway/test_internal_notification_marker_82888.py b/tests/gateway/test_internal_notification_marker.py similarity index 100% rename from tests/gateway/test_internal_notification_marker_82888.py rename to tests/gateway/test_internal_notification_marker.py diff --git a/tests/gateway/test_73771_media_resend_dedup.py b/tests/gateway/test_media_resend_dedup.py similarity index 100% rename from tests/gateway/test_73771_media_resend_dedup.py rename to tests/gateway/test_media_resend_dedup.py diff --git a/tests/gateway/test_73297_memory_flush_on_reset.py b/tests/gateway/test_memory_flush_on_reset.py similarity index 100% rename from tests/gateway/test_73297_memory_flush_on_reset.py rename to tests/gateway/test_memory_flush_on_reset.py diff --git a/tests/gateway/test_48031_model_switch_after_auto_reset.py b/tests/gateway/test_model_switch_after_auto_reset.py similarity index 100% rename from tests/gateway/test_48031_model_switch_after_auto_reset.py rename to tests/gateway/test_model_switch_after_auto_reset.py diff --git a/tests/gateway/test_64674_multiplex_primary_token_scope.py b/tests/gateway/test_multiplex_primary_token_scope.py similarity index 100% rename from tests/gateway/test_64674_multiplex_primary_token_scope.py rename to tests/gateway/test_multiplex_primary_token_scope.py diff --git a/tests/gateway/test_priority_path_compression_demotion_56391.py b/tests/gateway/test_priority_path_compression_demotion.py similarity index 100% rename from tests/gateway/test_priority_path_compression_demotion_56391.py rename to tests/gateway/test_priority_path_compression_demotion.py diff --git a/tests/test_profile_isolation_runtime.py b/tests/gateway/test_profile_isolation_runtime.py similarity index 100% rename from tests/test_profile_isolation_runtime.py rename to tests/gateway/test_profile_isolation_runtime.py diff --git a/tests/test_89315_replace_ownership_guard.py b/tests/gateway/test_replace_ownership_guard.py similarity index 100% rename from tests/test_89315_replace_ownership_guard.py rename to tests/gateway/test_replace_ownership_guard.py diff --git a/tests/gateway/test_35994_reset_button_deadlock.py b/tests/gateway/test_reset_button_deadlock.py similarity index 100% rename from tests/gateway/test_35994_reset_button_deadlock.py rename to tests/gateway/test_reset_button_deadlock.py diff --git a/tests/gateway/test_session_continuity_82616.py b/tests/gateway/test_session_continuity.py similarity index 100% rename from tests/gateway/test_session_continuity_82616.py rename to tests/gateway/test_session_continuity.py diff --git a/tests/gateway/test_session_split_brain_11016.py b/tests/gateway/test_session_split_brain.py similarity index 100% rename from tests/gateway/test_session_split_brain_11016.py rename to tests/gateway/test_session_split_brain.py diff --git a/tests/gateway/test_13121_shutdown_inflight_transcript_flush.py b/tests/gateway/test_shutdown_inflight_transcript_flush.py similarity index 100% rename from tests/gateway/test_13121_shutdown_inflight_transcript_flush.py rename to tests/gateway/test_shutdown_inflight_transcript_flush.py diff --git a/tests/gateway/test_silent_partial_delivery_95382.py b/tests/gateway/test_silent_partial_delivery.py similarity index 100% rename from tests/gateway/test_silent_partial_delivery_95382.py rename to tests/gateway/test_silent_partial_delivery.py diff --git a/tests/test_slack_thread_require_mention.py b/tests/gateway/test_slack_thread_require_mention.py similarity index 100% rename from tests/test_slack_thread_require_mention.py rename to tests/gateway/test_slack_thread_require_mention.py diff --git a/tests/gateway/test_25107_stale_base_url_api_mode.py b/tests/gateway/test_stale_base_url_api_mode.py similarity index 100% rename from tests/gateway/test_25107_stale_base_url_api_mode.py rename to tests/gateway/test_stale_base_url_api_mode.py diff --git a/tests/gateway/test_subagent_protection_30170.py b/tests/gateway/test_subagent_protection.py similarity index 100% rename from tests/gateway/test_subagent_protection_30170.py rename to tests/gateway/test_subagent_protection.py diff --git a/tests/gateway/test_telegram_closewait_limits_31599.py b/tests/gateway/test_telegram_closewait_limits.py similarity index 100% rename from tests/gateway/test_telegram_closewait_limits_31599.py rename to tests/gateway/test_telegram_closewait_limits.py diff --git a/tests/gateway/test_telegram_fallback_pool_release_71593.py b/tests/gateway/test_telegram_fallback_pool_release.py similarity index 100% rename from tests/gateway/test_telegram_fallback_pool_release_71593.py rename to tests/gateway/test_telegram_fallback_pool_release.py diff --git a/tests/gateway/test_telegram_prune_stale_topic_binding_31501.py b/tests/gateway/test_telegram_prune_stale_topic_binding.py similarity index 100% rename from tests/gateway/test_telegram_prune_stale_topic_binding_31501.py rename to tests/gateway/test_telegram_prune_stale_topic_binding.py diff --git a/tests/gateway/test_telegram_topic_profile_isolation_76423.py b/tests/gateway/test_telegram_topic_profile_isolation.py similarity index 100% rename from tests/gateway/test_telegram_topic_profile_isolation_76423.py rename to tests/gateway/test_telegram_topic_profile_isolation.py diff --git a/tests/gateway/test_telegram_topic_profile_routing_76423.py b/tests/gateway/test_telegram_topic_profile_routing.py similarity index 100% rename from tests/gateway/test_telegram_topic_profile_routing_76423.py rename to tests/gateway/test_telegram_topic_profile_routing.py diff --git a/tests/test_tini_shim.py b/tests/gateway/test_tini_shim.py similarity index 98% rename from tests/test_tini_shim.py rename to tests/gateway/test_tini_shim.py index 737cbaa8d1..aee5a63b6e 100644 --- a/tests/test_tini_shim.py +++ b/tests/gateway/test_tini_shim.py @@ -12,7 +12,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parents[1] +REPO_ROOT = Path(__file__).resolve().parents[2] SHIM = REPO_ROOT / "docker" / "tini-shim.sh" diff --git a/tests/gateway/test_transcript_read_failure_100788.py b/tests/gateway/test_transcript_read_failure.py similarity index 100% rename from tests/gateway/test_transcript_read_failure_100788.py rename to tests/gateway/test_transcript_read_failure.py diff --git a/tests/gateway/test_7100_transient_failure_transcript.py b/tests/gateway/test_transient_failure_transcript.py similarity index 100% rename from tests/gateway/test_7100_transient_failure_transcript.py rename to tests/gateway/test_transient_failure_transcript.py diff --git a/tests/gateway/test_watchdog_review_76354.py b/tests/gateway/test_watchdog_review.py similarity index 100% rename from tests/gateway/test_watchdog_review_76354.py rename to tests/gateway/test_watchdog_review.py diff --git a/tests/gateway/test_75349_whatsapp_multiplex_secret_scope.py b/tests/gateway/test_whatsapp_multiplex_secret_scope.py similarity index 100% rename from tests/gateway/test_75349_whatsapp_multiplex_secret_scope.py rename to tests/gateway/test_whatsapp_multiplex_secret_scope.py diff --git a/tests/test_yuanbao_integration.py b/tests/gateway/test_yuanbao_integration.py similarity index 99% rename from tests/test_yuanbao_integration.py rename to tests/gateway/test_yuanbao_integration.py index 7382dcd552..b40c1ea0c2 100644 --- a/tests/test_yuanbao_integration.py +++ b/tests/gateway/test_yuanbao_integration.py @@ -15,7 +15,7 @@ import sys import os # 确保 hermes-agent 根目录在 sys.path 中 -_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) if _REPO_ROOT not in sys.path: sys.path.insert(0, _REPO_ROOT) diff --git a/tests/test_yuanbao_markdown.py b/tests/gateway/test_yuanbao_markdown.py similarity index 96% rename from tests/test_yuanbao_markdown.py rename to tests/gateway/test_yuanbao_markdown.py index 1d038e960a..42ce50028e 100644 --- a/tests/test_yuanbao_markdown.py +++ b/tests/gateway/test_yuanbao_markdown.py @@ -3,10 +3,10 @@ test_yuanbao_markdown.py - Unit tests for yuanbao_markdown.py Run (no pytest needed): cd /root/.openclaw/workspace/hermes-agent - python3 tests/test_yuanbao_markdown.py -v + python3 tests/gateway/test_yuanbao_markdown.py -v Or with pytest if available: - python3 -m pytest tests/test_yuanbao_markdown.py -v + python3 -m pytest tests/gateway/test_yuanbao_markdown.py -v """ import sys @@ -14,7 +14,7 @@ import os import unittest # Ensure project root is on the path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from gateway.platforms import helpers as _mdchunk from gateway.platforms.yuanbao import MarkdownProcessor diff --git a/tests/test_yuanbao_pipeline.py b/tests/gateway/test_yuanbao_pipeline.py similarity index 99% rename from tests/test_yuanbao_pipeline.py rename to tests/gateway/test_yuanbao_pipeline.py index f15a5074fa..a8ee914e34 100644 --- a/tests/test_yuanbao_pipeline.py +++ b/tests/gateway/test_yuanbao_pipeline.py @@ -16,7 +16,7 @@ import os import json # Ensure project root is on the path -_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) if _REPO_ROOT not in sys.path: sys.path.insert(0, _REPO_ROOT) diff --git a/tests/test_yuanbao_proto.py b/tests/gateway/test_yuanbao_proto.py similarity index 99% rename from tests/test_yuanbao_proto.py rename to tests/gateway/test_yuanbao_proto.py index dcf1f92f2c..faf6f678ae 100644 --- a/tests/test_yuanbao_proto.py +++ b/tests/gateway/test_yuanbao_proto.py @@ -15,7 +15,7 @@ import sys import os # 确保 hermes-agent 根目录在 sys.path 中 -_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) if _REPO_ROOT not in sys.path: sys.path.insert(0, _REPO_ROOT) diff --git a/tests/test_yuanbao_reconnect_set_active.py b/tests/gateway/test_yuanbao_reconnect_set_active.py similarity index 97% rename from tests/test_yuanbao_reconnect_set_active.py rename to tests/gateway/test_yuanbao_reconnect_set_active.py index 9bb8a02040..b52bc66e47 100644 --- a/tests/test_yuanbao_reconnect_set_active.py +++ b/tests/gateway/test_yuanbao_reconnect_set_active.py @@ -12,7 +12,7 @@ import os import asyncio from unittest.mock import AsyncMock, MagicMock, patch -_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) if _REPO_ROOT not in sys.path: sys.path.insert(0, _REPO_ROOT) diff --git a/tests/test_yuanbao_shutdown.py b/tests/gateway/test_yuanbao_shutdown.py similarity index 97% rename from tests/test_yuanbao_shutdown.py rename to tests/gateway/test_yuanbao_shutdown.py index be535f46c7..0b65cf53de 100644 --- a/tests/test_yuanbao_shutdown.py +++ b/tests/gateway/test_yuanbao_shutdown.py @@ -17,7 +17,7 @@ import sys import os import asyncio -_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) if _REPO_ROOT not in sys.path: sys.path.insert(0, _REPO_ROOT) diff --git a/tests/hermes_cli/conftest.py b/tests/hermes_cli/conftest.py index 60728cdc8d..1db3887b7e 100644 --- a/tests/hermes_cli/conftest.py +++ b/tests/hermes_cli/conftest.py @@ -85,3 +85,49 @@ def isolated_update_runtime(monkeypatch, tmp_path, request): monkeypatch.setattr(update_cmd_fleet, "_restart_macos_launchd_gateways", lambda *a, **k: None) monkeypatch.setattr(update_inventory, "collect_runtime_inventory", lambda: None) monkeypatch.setattr(update_receipt, "collect_fleet_versions", lambda *a, **k: []) + + +# ---- prompt_toolkit / capsys isolation ---- +# ``cli._cprint`` renders through ``prompt_toolkit.print_formatted_text``, +# which — when called with no explicit ``output=`` — lazily creates an +# ``Output`` from ``sys.stdout`` **and caches it on the process-global default +# ``AppSession``** (``prompt_toolkit.application.current._current_app_session``, +# a ``ContextVar`` with a module-level default). The cache is keyed to nothing +# and never re-reads ``sys.stdout``. +# +# Under pytest, ``capsys`` swaps ``sys.stdout`` for a fresh buffer per test. +# So the first CLI test that emits through ``_cprint`` (e.g. one exercising +# ``/queue``, which prints a "Queued: …" line) locks prompt_toolkit's cached +# output onto *its* captured stdout. Every later ``capsys`` test that asserts +# on ``_cprint`` output then reads an empty buffer, because the render went to +# the first test's now-dead capture target. That is the mechanism behind the +# order-dependent ``test_resume_quiet_stderr`` failure: it passes in isolation +# and in its own file, but fails in a full ``tests/cli`` run. +# +# Reset the cached output before every CLI test so each one re-creates a fresh +# prompt_toolkit ``Output`` bound to its own ``sys.stdout`` on first use. This +# is a no-op when prompt_toolkit isn't importable and cheap otherwise (the +# property re-creates lazily). + + +@pytest.fixture(autouse=True) +def _reset_prompt_toolkit_output_cache(): + """Clear prompt_toolkit's cached AppSession output around each CLI test. + + See the module docstring for the capsys/prompt_toolkit interaction this + guards against. + """ + + def _clear() -> None: + try: + from prompt_toolkit.application.current import get_app_session + + get_app_session()._output = None + except Exception: + # prompt_toolkit not importable / internal shape changed — the + # tests that rely on this simply keep their prior behavior. + pass + + _clear() + yield + _clear() diff --git a/tests/test_active_session_exclusivity.py b/tests/hermes_cli/test_active_session_exclusivity.py similarity index 100% rename from tests/test_active_session_exclusivity.py rename to tests/hermes_cli/test_active_session_exclusivity.py diff --git a/tests/test_audio_playback_guard.py b/tests/hermes_cli/test_audio_playback_guard.py similarity index 98% rename from tests/test_audio_playback_guard.py rename to tests/hermes_cli/test_audio_playback_guard.py index c20c4cbe9e..4250be844a 100644 --- a/tests/test_audio_playback_guard.py +++ b/tests/hermes_cli/test_audio_playback_guard.py @@ -2,7 +2,7 @@ The incident: a plain test run spoke the words "partial answer complete" out of a developer's speakers. That string is a fake ``final_response`` from -``tests/test_tui_gateway_server.py``. The route was entirely in-process — no +``tests/tui_gateway/test_tui_gateway_server.py``. The route was entirely in-process — no leaked shell variable, so ``scripts/run_tests.sh``'s ``env -i`` was no help: 1. A test drives the ``voice.toggle`` RPC with ``action="tts"``. The diff --git a/tests/hermes_cli/test_auth_toctou_file_modes.py b/tests/hermes_cli/test_auth_toctou_file_modes.py index a9d92c2666..084f3dea63 100644 --- a/tests/hermes_cli/test_auth_toctou_file_modes.py +++ b/tests/hermes_cli/test_auth_toctou_file_modes.py @@ -8,7 +8,7 @@ create and chmod the file existed at the process umask (typically ``0o644``), briefly exposing OAuth tokens to other local users on multi-user hosts. The writers now go through ``utils.atomic_json_write(mode=0o600)`` whose mkstemp temp file is ``O_EXCL`` at 0600 on creation (the cross-writer invariant lives in -``tests/test_private_credential_writers.py``). +``tests/agent/test_private_credential_writers.py``). These tests stay green only while the token file and its parent directory end up at ``0o600`` / ``0o700`` after every write. POSIX-only — the mode-bit diff --git a/tests/cli/test_bang_shell_mode.py b/tests/hermes_cli/test_bang_shell_mode.py similarity index 100% rename from tests/cli/test_bang_shell_mode.py rename to tests/hermes_cli/test_bang_shell_mode.py diff --git a/tests/cli/test_bracketed_paste_timeout.py b/tests/hermes_cli/test_bracketed_paste_timeout.py similarity index 100% rename from tests/cli/test_bracketed_paste_timeout.py rename to tests/hermes_cli/test_bracketed_paste_timeout.py diff --git a/tests/cli/test_branch_command.py b/tests/hermes_cli/test_branch_command.py similarity index 100% rename from tests/cli/test_branch_command.py rename to tests/hermes_cli/test_branch_command.py diff --git a/tests/cli/test_busy_input_mode_command.py b/tests/hermes_cli/test_busy_input_mode_command.py similarity index 100% rename from tests/cli/test_busy_input_mode_command.py rename to tests/hermes_cli/test_busy_input_mode_command.py diff --git a/tests/cli/test_chat_q_exit_clear.py b/tests/hermes_cli/test_chat_q_exit_clear.py similarity index 100% rename from tests/cli/test_chat_q_exit_clear.py rename to tests/hermes_cli/test_chat_q_exit_clear.py diff --git a/tests/cli/test_cli_active_agent_ref_wiring.py b/tests/hermes_cli/test_cli_active_agent_ref_wiring.py similarity index 100% rename from tests/cli/test_cli_active_agent_ref_wiring.py rename to tests/hermes_cli/test_cli_active_agent_ref_wiring.py diff --git a/tests/cli/test_cli_approval_ui.py b/tests/hermes_cli/test_cli_approval_ui.py similarity index 100% rename from tests/cli/test_cli_approval_ui.py rename to tests/hermes_cli/test_cli_approval_ui.py diff --git a/tests/cli/test_cli_async_delegation_delivery.py b/tests/hermes_cli/test_cli_async_delegation_delivery.py similarity index 100% rename from tests/cli/test_cli_async_delegation_delivery.py rename to tests/hermes_cli/test_cli_async_delegation_delivery.py diff --git a/tests/cli/test_cli_background_busy_path.py b/tests/hermes_cli/test_cli_background_busy_path.py similarity index 98% rename from tests/cli/test_cli_background_busy_path.py rename to tests/hermes_cli/test_cli_background_busy_path.py index 9af07b0da1..e22ce84f13 100644 --- a/tests/cli/test_cli_background_busy_path.py +++ b/tests/hermes_cli/test_cli_background_busy_path.py @@ -15,7 +15,7 @@ the UI thread; the command's own ``CommandDef`` already declares never consulted. These tests exercise the detector without starting a prompt_toolkit app, -mirroring tests/cli/test_cli_steer_busy_path.py. +mirroring tests/hermes_cli/test_cli_steer_busy_path.py. """ from __future__ import annotations diff --git a/tests/cli/test_cli_background_status_indicator.py b/tests/hermes_cli/test_cli_background_status_indicator.py similarity index 100% rename from tests/cli/test_cli_background_status_indicator.py rename to tests/hermes_cli/test_cli_background_status_indicator.py diff --git a/tests/cli/test_cli_background_tui_refresh.py b/tests/hermes_cli/test_cli_background_tui_refresh.py similarity index 100% rename from tests/cli/test_cli_background_tui_refresh.py rename to tests/hermes_cli/test_cli_background_tui_refresh.py diff --git a/tests/cli/test_cli_bracketed_paste_sanitizer.py b/tests/hermes_cli/test_cli_bracketed_paste_sanitizer.py similarity index 100% rename from tests/cli/test_cli_bracketed_paste_sanitizer.py rename to tests/hermes_cli/test_cli_bracketed_paste_sanitizer.py diff --git a/tests/cli/test_cli_browser_connect.py b/tests/hermes_cli/test_cli_browser_connect.py similarity index 100% rename from tests/cli/test_cli_browser_connect.py rename to tests/hermes_cli/test_cli_browser_connect.py diff --git a/tests/cli/test_cli_clarify_batch.py b/tests/hermes_cli/test_cli_clarify_batch.py similarity index 99% rename from tests/cli/test_cli_clarify_batch.py rename to tests/hermes_cli/test_cli_clarify_batch.py index a19575956c..809dbe42bd 100644 --- a/tests/cli/test_cli_clarify_batch.py +++ b/tests/hermes_cli/test_cli_clarify_batch.py @@ -5,7 +5,7 @@ thread (the way the agent thread calls it) and simulates the keybinding handlers by calling the same helper methods they call (``_clarify_batch_set_active`` for Tab, ``_clarify_batch_enter`` for Enter, ``_clarify_batch_lock`` for the freetext submit path). No real -terminal needed — mirrors tests/cli/test_cli_approval_ui.py. +terminal needed — mirrors tests/hermes_cli/test_cli_approval_ui.py. """ import json diff --git a/tests/cli/test_cli_cmd_backspace.py b/tests/hermes_cli/test_cli_cmd_backspace.py similarity index 100% rename from tests/cli/test_cli_cmd_backspace.py rename to tests/hermes_cli/test_cli_cmd_backspace.py diff --git a/tests/cli/test_cli_codex_context_reference.py b/tests/hermes_cli/test_cli_codex_context_reference.py similarity index 100% rename from tests/cli/test_cli_codex_context_reference.py rename to tests/hermes_cli/test_cli_codex_context_reference.py diff --git a/tests/cli/test_cli_context_warning.py b/tests/hermes_cli/test_cli_context_warning.py similarity index 100% rename from tests/cli/test_cli_context_warning.py rename to tests/hermes_cli/test_cli_context_warning.py diff --git a/tests/cli/test_cli_copy_command.py b/tests/hermes_cli/test_cli_copy_command.py similarity index 100% rename from tests/cli/test_cli_copy_command.py rename to tests/hermes_cli/test_cli_copy_command.py diff --git a/tests/cli/test_cli_delegate_background_notice.py b/tests/hermes_cli/test_cli_delegate_background_notice.py similarity index 100% rename from tests/cli/test_cli_delegate_background_notice.py rename to tests/hermes_cli/test_cli_delegate_background_notice.py diff --git a/tests/cli/test_cli_extension_hooks.py b/tests/hermes_cli/test_cli_extension_hooks.py similarity index 100% rename from tests/cli/test_cli_extension_hooks.py rename to tests/hermes_cli/test_cli_extension_hooks.py diff --git a/tests/cli/test_cli_external_editor.py b/tests/hermes_cli/test_cli_external_editor.py similarity index 100% rename from tests/cli/test_cli_external_editor.py rename to tests/hermes_cli/test_cli_external_editor.py diff --git a/tests/cli/test_cli_file_drop.py b/tests/hermes_cli/test_cli_file_drop.py similarity index 100% rename from tests/cli/test_cli_file_drop.py rename to tests/hermes_cli/test_cli_file_drop.py diff --git a/tests/cli/test_cli_first_run_setup.py b/tests/hermes_cli/test_cli_first_run_setup.py similarity index 100% rename from tests/cli/test_cli_first_run_setup.py rename to tests/hermes_cli/test_cli_first_run_setup.py diff --git a/tests/cli/test_cli_force_redraw.py b/tests/hermes_cli/test_cli_force_redraw.py similarity index 100% rename from tests/cli/test_cli_force_redraw.py rename to tests/hermes_cli/test_cli_force_redraw.py diff --git a/tests/cli/test_cli_goal_interrupt.py b/tests/hermes_cli/test_cli_goal_interrupt.py similarity index 100% rename from tests/cli/test_cli_goal_interrupt.py rename to tests/hermes_cli/test_cli_goal_interrupt.py diff --git a/tests/cli/test_cli_goal_kick_prompt.py b/tests/hermes_cli/test_cli_goal_kick_prompt.py similarity index 100% rename from tests/cli/test_cli_goal_kick_prompt.py rename to tests/hermes_cli/test_cli_goal_kick_prompt.py diff --git a/tests/cli/test_cli_goal_parked_resume.py b/tests/hermes_cli/test_cli_goal_parked_resume.py similarity index 100% rename from tests/cli/test_cli_goal_parked_resume.py rename to tests/hermes_cli/test_cli_goal_parked_resume.py diff --git a/tests/cli/test_cli_image_command.py b/tests/hermes_cli/test_cli_image_command.py similarity index 100% rename from tests/cli/test_cli_image_command.py rename to tests/hermes_cli/test_cli_image_command.py diff --git a/tests/cli/test_cli_init.py b/tests/hermes_cli/test_cli_init.py similarity index 99% rename from tests/cli/test_cli_init.py rename to tests/hermes_cli/test_cli_init.py index 059508991f..1438cd08ee 100644 --- a/tests/cli/test_cli_init.py +++ b/tests/hermes_cli/test_cli_init.py @@ -256,7 +256,7 @@ class TestPromptToolkitTerminalCompatibility: The native-Windows arm (``_terminal_may_leak_cpr() is False``, plus the ``PROMPT_TOOLKIT_NO_CPR`` override that outranks it) lives in - ``tests/cli/test_cpr_local_leak.py`` under ``windows_only``, where it + ``tests/hermes_cli/test_cpr_local_leak.py`` under ``windows_only``, where it runs against a real Windows console. """ from cli import _terminal_may_leak_cpr diff --git a/tests/cli/test_cli_insights_command.py b/tests/hermes_cli/test_cli_insights_command.py similarity index 100% rename from tests/cli/test_cli_insights_command.py rename to tests/hermes_cli/test_cli_insights_command.py diff --git a/tests/cli/test_cli_interrupt_ack_race.py b/tests/hermes_cli/test_cli_interrupt_ack_race.py similarity index 100% rename from tests/cli/test_cli_interrupt_ack_race.py rename to tests/hermes_cli/test_cli_interrupt_ack_race.py diff --git a/tests/cli/test_cli_interrupt_drain_regression.py b/tests/hermes_cli/test_cli_interrupt_drain_regression.py similarity index 100% rename from tests/cli/test_cli_interrupt_drain_regression.py rename to tests/hermes_cli/test_cli_interrupt_drain_regression.py diff --git a/tests/cli/test_cli_interrupt_subagent.py b/tests/hermes_cli/test_cli_interrupt_subagent.py similarity index 100% rename from tests/cli/test_cli_interrupt_subagent.py rename to tests/hermes_cli/test_cli_interrupt_subagent.py diff --git a/tests/cli/test_cli_light_mode.py b/tests/hermes_cli/test_cli_light_mode.py similarity index 100% rename from tests/cli/test_cli_light_mode.py rename to tests/hermes_cli/test_cli_light_mode.py diff --git a/tests/cli/test_cli_loading_indicator.py b/tests/hermes_cli/test_cli_loading_indicator.py similarity index 100% rename from tests/cli/test_cli_loading_indicator.py rename to tests/hermes_cli/test_cli_loading_indicator.py diff --git a/tests/test_cli_manual_compress.py b/tests/hermes_cli/test_cli_manual_compress.py similarity index 100% rename from tests/test_cli_manual_compress.py rename to tests/hermes_cli/test_cli_manual_compress.py diff --git a/tests/cli/test_cli_markdown_rendering.py b/tests/hermes_cli/test_cli_markdown_rendering.py similarity index 100% rename from tests/cli/test_cli_markdown_rendering.py rename to tests/hermes_cli/test_cli_markdown_rendering.py diff --git a/tests/cli/test_cli_mcp_config_watch.py b/tests/hermes_cli/test_cli_mcp_config_watch.py similarity index 100% rename from tests/cli/test_cli_mcp_config_watch.py rename to tests/hermes_cli/test_cli_mcp_config_watch.py diff --git a/tests/cli/test_cli_new_session.py b/tests/hermes_cli/test_cli_new_session.py similarity index 99% rename from tests/cli/test_cli_new_session.py rename to tests/hermes_cli/test_cli_new_session.py index f35e218b6f..cbad4d58ac 100644 --- a/tests/cli/test_cli_new_session.py +++ b/tests/hermes_cli/test_cli_new_session.py @@ -135,7 +135,7 @@ def _prepare_cli_with_active_session(tmp_path): # Bypass the destructive-slash confirmation gate — these tests focus on # the new-session mechanics, not the confirm prompt itself (covered in - # tests/cli/test_destructive_slash_confirm.py). + # tests/hermes_cli/test_destructive_slash_confirm.py). cli._confirm_destructive_slash = lambda *_a, **_kw: "once" return cli diff --git a/tests/hermes_cli/test_cli_partial_update_hint_96900.py b/tests/hermes_cli/test_cli_partial_update_hint.py similarity index 100% rename from tests/hermes_cli/test_cli_partial_update_hint_96900.py rename to tests/hermes_cli/test_cli_partial_update_hint.py diff --git a/tests/cli/test_cli_pet_pane.py b/tests/hermes_cli/test_cli_pet_pane.py similarity index 100% rename from tests/cli/test_cli_pet_pane.py rename to tests/hermes_cli/test_cli_pet_pane.py diff --git a/tests/cli/test_cli_prefix_matching.py b/tests/hermes_cli/test_cli_prefix_matching.py similarity index 100% rename from tests/cli/test_cli_prefix_matching.py rename to tests/hermes_cli/test_cli_prefix_matching.py diff --git a/tests/cli/test_cli_preloaded_skills.py b/tests/hermes_cli/test_cli_preloaded_skills.py similarity index 100% rename from tests/cli/test_cli_preloaded_skills.py rename to tests/hermes_cli/test_cli_preloaded_skills.py diff --git a/tests/cli/test_cli_provider_resolution.py b/tests/hermes_cli/test_cli_provider_resolution.py similarity index 100% rename from tests/cli/test_cli_provider_resolution.py rename to tests/hermes_cli/test_cli_provider_resolution.py diff --git a/tests/cli/test_cli_queue_paste.py b/tests/hermes_cli/test_cli_queue_paste.py similarity index 100% rename from tests/cli/test_cli_queue_paste.py rename to tests/hermes_cli/test_cli_queue_paste.py diff --git a/tests/test_cli_quiet_stdout_leak.py b/tests/hermes_cli/test_cli_quiet_stdout_leak.py similarity index 100% rename from tests/test_cli_quiet_stdout_leak.py rename to tests/hermes_cli/test_cli_quiet_stdout_leak.py diff --git a/tests/cli/test_cli_reload_skills.py b/tests/hermes_cli/test_cli_reload_skills.py similarity index 100% rename from tests/cli/test_cli_reload_skills.py rename to tests/hermes_cli/test_cli_reload_skills.py diff --git a/tests/cli/test_cli_resume_command.py b/tests/hermes_cli/test_cli_resume_command.py similarity index 100% rename from tests/cli/test_cli_resume_command.py rename to tests/hermes_cli/test_cli_resume_command.py diff --git a/tests/cli/test_cli_retry.py b/tests/hermes_cli/test_cli_retry.py similarity index 100% rename from tests/cli/test_cli_retry.py rename to tests/hermes_cli/test_cli_retry.py diff --git a/tests/cli/test_cli_save_config_value.py b/tests/hermes_cli/test_cli_save_config_value.py similarity index 100% rename from tests/cli/test_cli_save_config_value.py rename to tests/hermes_cli/test_cli_save_config_value.py diff --git a/tests/cli/test_cli_secret_capture.py b/tests/hermes_cli/test_cli_secret_capture.py similarity index 100% rename from tests/cli/test_cli_secret_capture.py rename to tests/hermes_cli/test_cli_secret_capture.py diff --git a/tests/test_cli_session_store_shared_registry.py b/tests/hermes_cli/test_cli_session_store_shared_registry.py similarity index 100% rename from tests/test_cli_session_store_shared_registry.py rename to tests/hermes_cli/test_cli_session_store_shared_registry.py diff --git a/tests/cli/test_cli_shift_enter_newline.py b/tests/hermes_cli/test_cli_shift_enter_newline.py similarity index 100% rename from tests/cli/test_cli_shift_enter_newline.py rename to tests/hermes_cli/test_cli_shift_enter_newline.py diff --git a/tests/cli/test_cli_shutdown_memory_messages.py b/tests/hermes_cli/test_cli_shutdown_memory_messages.py similarity index 100% rename from tests/cli/test_cli_shutdown_memory_messages.py rename to tests/hermes_cli/test_cli_shutdown_memory_messages.py diff --git a/tests/test_cli_skin_integration.py b/tests/hermes_cli/test_cli_skin_integration.py similarity index 86% rename from tests/test_cli_skin_integration.py rename to tests/hermes_cli/test_cli_skin_integration.py index 02d7b11dc0..c094bcacfa 100644 --- a/tests/test_cli_skin_integration.py +++ b/tests/hermes_cli/test_cli_skin_integration.py @@ -45,6 +45,20 @@ class TestCliSkinPromptIntegration: assert cli._get_tui_prompt_fragments() == [("class:sudo-prompt", "🔑 ⚔ ")] + def test_narrow_terminals_compact_voice_recording_prompt_fragments(self): + cli = _make_cli_stub() + cli._voice_recording = True + cli._voice_recorder = SimpleNamespace(current_rms=3000) + + with patch.object(HermesCLI, "_get_tui_terminal_width", return_value=50): + frags = cli._get_tui_prompt_fragments() + + assert frags[0][0] == "class:voice-recording" + assert frags[0][1].startswith("●") + assert "❯" not in frags[0][1] + + + def test_build_tui_style_dict_uses_skin_overrides(self): cli = _make_cli_stub() @@ -111,3 +125,6 @@ class TestAnsiRichTextHelper: text = _rich_text_from_ansi("[notatag] literal") assert text.plain == "[notatag] literal" + def test_strips_ansi_but_keeps_plain_text(self): + text = _rich_text_from_ansi("\x1b[31mred\x1b[0m") + assert text.plain == "red" diff --git a/tests/cli/test_cli_status_bar.py b/tests/hermes_cli/test_cli_status_bar.py similarity index 100% rename from tests/cli/test_cli_status_bar.py rename to tests/hermes_cli/test_cli_status_bar.py diff --git a/tests/cli/test_cli_status_bar_goal.py b/tests/hermes_cli/test_cli_status_bar_goal.py similarity index 100% rename from tests/cli/test_cli_status_bar_goal.py rename to tests/hermes_cli/test_cli_status_bar_goal.py diff --git a/tests/cli/test_cli_status_command.py b/tests/hermes_cli/test_cli_status_command.py similarity index 100% rename from tests/cli/test_cli_status_command.py rename to tests/hermes_cli/test_cli_status_command.py diff --git a/tests/cli/test_cli_steer_busy_path.py b/tests/hermes_cli/test_cli_steer_busy_path.py similarity index 100% rename from tests/cli/test_cli_steer_busy_path.py rename to tests/hermes_cli/test_cli_steer_busy_path.py diff --git a/tests/cli/test_cli_terminal_response_sanitizer.py b/tests/hermes_cli/test_cli_terminal_response_sanitizer.py similarity index 100% rename from tests/cli/test_cli_terminal_response_sanitizer.py rename to tests/hermes_cli/test_cli_terminal_response_sanitizer.py diff --git a/tests/cli/test_cli_terminal_shortcuts.py b/tests/hermes_cli/test_cli_terminal_shortcuts.py similarity index 100% rename from tests/cli/test_cli_terminal_shortcuts.py rename to tests/hermes_cli/test_cli_terminal_shortcuts.py diff --git a/tests/cli/test_cli_tools_command.py b/tests/hermes_cli/test_cli_tools_command.py similarity index 100% rename from tests/cli/test_cli_tools_command.py rename to tests/hermes_cli/test_cli_tools_command.py diff --git a/tests/cli/test_cli_user_message_preview.py b/tests/hermes_cli/test_cli_user_message_preview.py similarity index 100% rename from tests/cli/test_cli_user_message_preview.py rename to tests/hermes_cli/test_cli_user_message_preview.py diff --git a/tests/cli/test_cli_yolo_resume_persistence.py b/tests/hermes_cli/test_cli_yolo_resume_persistence.py similarity index 100% rename from tests/cli/test_cli_yolo_resume_persistence.py rename to tests/hermes_cli/test_cli_yolo_resume_persistence.py diff --git a/tests/cli/test_cli_yolo_toggle.py b/tests/hermes_cli/test_cli_yolo_toggle.py similarity index 100% rename from tests/cli/test_cli_yolo_toggle.py rename to tests/hermes_cli/test_cli_yolo_toggle.py diff --git a/tests/test_code_skew.py b/tests/hermes_cli/test_code_skew.py similarity index 100% rename from tests/test_code_skew.py rename to tests/hermes_cli/test_code_skew.py diff --git a/tests/cli/test_command_palette.py b/tests/hermes_cli/test_command_palette.py similarity index 100% rename from tests/cli/test_command_palette.py rename to tests/hermes_cli/test_command_palette.py diff --git a/tests/test_compat_manifest_targets.py b/tests/hermes_cli/test_compat_manifest_targets.py similarity index 98% rename from tests/test_compat_manifest_targets.py rename to tests/hermes_cli/test_compat_manifest_targets.py index 461636586e..41ca26f557 100644 --- a/tests/test_compat_manifest_targets.py +++ b/tests/hermes_cli/test_compat_manifest_targets.py @@ -16,7 +16,7 @@ from pathlib import Path import pytest -ROOT = Path(__file__).resolve().parent.parent +ROOT = Path(__file__).resolve().parent.parent.parent MANIFEST = ROOT / "compat_manifest.json" # This file resolves every pointer on purpose; the once-per-name plugin warning is expected here. diff --git a/tests/cli/test_completion_backlog.py b/tests/hermes_cli/test_completion_backlog.py similarity index 100% rename from tests/cli/test_completion_backlog.py rename to tests/hermes_cli/test_completion_backlog.py diff --git a/tests/cli/test_compress_flags.py b/tests/hermes_cli/test_compress_flags.py similarity index 100% rename from tests/cli/test_compress_flags.py rename to tests/hermes_cli/test_compress_flags.py diff --git a/tests/cli/test_compress_focus.py b/tests/hermes_cli/test_compress_focus.py similarity index 100% rename from tests/cli/test_compress_focus.py rename to tests/hermes_cli/test_compress_focus.py diff --git a/tests/cli/test_compress_here.py b/tests/hermes_cli/test_compress_here.py similarity index 100% rename from tests/cli/test_compress_here.py rename to tests/hermes_cli/test_compress_here.py diff --git a/tests/cli/test_compress_type_ahead.py b/tests/hermes_cli/test_compress_type_ahead.py similarity index 100% rename from tests/cli/test_compress_type_ahead.py rename to tests/hermes_cli/test_compress_type_ahead.py diff --git a/tests/cli/test_cpr_local_leak.py b/tests/hermes_cli/test_cpr_local_leak.py similarity index 100% rename from tests/cli/test_cpr_local_leak.py rename to tests/hermes_cli/test_cpr_local_leak.py diff --git a/tests/cli/test_cprint_bg_thread.py b/tests/hermes_cli/test_cprint_bg_thread.py similarity index 100% rename from tests/cli/test_cprint_bg_thread.py rename to tests/hermes_cli/test_cprint_bg_thread.py diff --git a/tests/cli/test_ctrl_enter_newline.py b/tests/hermes_cli/test_ctrl_enter_newline.py similarity index 100% rename from tests/cli/test_ctrl_enter_newline.py rename to tests/hermes_cli/test_ctrl_enter_newline.py diff --git a/tests/cli/test_cwd_env_respect.py b/tests/hermes_cli/test_cwd_env_respect.py similarity index 100% rename from tests/cli/test_cwd_env_respect.py rename to tests/hermes_cli/test_cwd_env_respect.py diff --git a/tests/cli/test_destructive_slash_confirm.py b/tests/hermes_cli/test_destructive_slash_confirm.py similarity index 100% rename from tests/cli/test_destructive_slash_confirm.py rename to tests/hermes_cli/test_destructive_slash_confirm.py diff --git a/tests/cli/test_destructive_slash_inline_skip_e2e.py b/tests/hermes_cli/test_destructive_slash_inline_skip_e2e.py similarity index 100% rename from tests/cli/test_destructive_slash_inline_skip_e2e.py rename to tests/hermes_cli/test_destructive_slash_inline_skip_e2e.py diff --git a/tests/test_empty_session_hygiene.py b/tests/hermes_cli/test_empty_session_hygiene.py similarity index 100% rename from tests/test_empty_session_hygiene.py rename to tests/hermes_cli/test_empty_session_hygiene.py diff --git a/tests/hermes_cli/test_ensure_hermes_home_uid_34107.py b/tests/hermes_cli/test_ensure_hermes_home_uid.py similarity index 100% rename from tests/hermes_cli/test_ensure_hermes_home_uid_34107.py rename to tests/hermes_cli/test_ensure_hermes_home_uid.py diff --git a/tests/test_env_loader_op_bootstrap.py b/tests/hermes_cli/test_env_loader_op_bootstrap.py similarity index 99% rename from tests/test_env_loader_op_bootstrap.py rename to tests/hermes_cli/test_env_loader_op_bootstrap.py index f58dd11334..a562cd5244 100644 --- a/tests/test_env_loader_op_bootstrap.py +++ b/tests/hermes_cli/test_env_loader_op_bootstrap.py @@ -29,7 +29,7 @@ from unittest import mock import pytest # Make the worktree importable without depending on the installed wheel. -ROOT = Path(__file__).resolve().parents[1] +ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) diff --git a/tests/cli/test_exit_delete_session.py b/tests/hermes_cli/test_exit_delete_session.py similarity index 100% rename from tests/cli/test_exit_delete_session.py rename to tests/hermes_cli/test_exit_delete_session.py diff --git a/tests/cli/test_exit_summary_resume_hint.py b/tests/hermes_cli/test_exit_summary_resume_hint.py similarity index 100% rename from tests/cli/test_exit_summary_resume_hint.py rename to tests/hermes_cli/test_exit_summary_resume_hint.py diff --git a/tests/cli/test_exit_watchdog_signal_arm.py b/tests/hermes_cli/test_exit_watchdog_signal_arm.py similarity index 100% rename from tests/cli/test_exit_watchdog_signal_arm.py rename to tests/hermes_cli/test_exit_watchdog_signal_arm.py diff --git a/tests/cli/test_fast_command.py b/tests/hermes_cli/test_fast_command.py similarity index 100% rename from tests/cli/test_fast_command.py rename to tests/hermes_cli/test_fast_command.py diff --git a/tests/cli/test_finite_chat_delegation.py b/tests/hermes_cli/test_finite_chat_delegation.py similarity index 100% rename from tests/cli/test_finite_chat_delegation.py rename to tests/hermes_cli/test_finite_chat_delegation.py diff --git a/tests/cli/test_focus_view.py b/tests/hermes_cli/test_focus_view.py similarity index 100% rename from tests/cli/test_focus_view.py rename to tests/hermes_cli/test_focus_view.py diff --git a/tests/test_gitlock.py b/tests/hermes_cli/test_gitlock.py similarity index 100% rename from tests/test_gitlock.py rename to tests/hermes_cli/test_gitlock.py diff --git a/tests/hermes_cli/test_25106_global_switch_persists_base_url_api_mode.py b/tests/hermes_cli/test_global_switch_persists_base_url_api_mode.py similarity index 100% rename from tests/hermes_cli/test_25106_global_switch_persists_base_url_api_mode.py rename to tests/hermes_cli/test_global_switch_persists_base_url_api_mode.py diff --git a/tests/cli/test_handoff_cleanup_race.py b/tests/hermes_cli/test_handoff_cleanup_race.py similarity index 100% rename from tests/cli/test_handoff_cleanup_race.py rename to tests/hermes_cli/test_handoff_cleanup_race.py diff --git a/tests/cli/test_handoff_slow_dispatch_timeout.py b/tests/hermes_cli/test_handoff_slow_dispatch_timeout.py similarity index 100% rename from tests/cli/test_handoff_slow_dispatch_timeout.py rename to tests/hermes_cli/test_handoff_slow_dispatch_timeout.py diff --git a/tests/cli/test_indicator_command.py b/tests/hermes_cli/test_indicator_command.py similarity index 100% rename from tests/cli/test_indicator_command.py rename to tests/hermes_cli/test_indicator_command.py diff --git a/tests/cli/test_interrupt_output_history_regression.py b/tests/hermes_cli/test_interrupt_output_history_regression.py similarity index 100% rename from tests/cli/test_interrupt_output_history_regression.py rename to tests/hermes_cli/test_interrupt_output_history_regression.py diff --git a/tests/test_iron_proxy_cli.py b/tests/hermes_cli/test_iron_proxy_cli.py similarity index 100% rename from tests/test_iron_proxy_cli.py rename to tests/hermes_cli/test_iron_proxy_cli.py diff --git a/tests/cli/test_kitty_keypad_parity.py b/tests/hermes_cli/test_kitty_keypad_parity.py similarity index 100% rename from tests/cli/test_kitty_keypad_parity.py rename to tests/hermes_cli/test_kitty_keypad_parity.py diff --git a/tests/test_lazy_secrets_dispatch.py b/tests/hermes_cli/test_lazy_secrets_dispatch.py similarity index 98% rename from tests/test_lazy_secrets_dispatch.py rename to tests/hermes_cli/test_lazy_secrets_dispatch.py index e48a613588..32e925001d 100644 --- a/tests/test_lazy_secrets_dispatch.py +++ b/tests/hermes_cli/test_lazy_secrets_dispatch.py @@ -35,7 +35,7 @@ def _run_hermes(args: list[str], timeout: int = 30) -> subprocess.CompletedProce still exercises its full parser/dispatch/fetch code and exits 1, which the assertions already accept. """ - repo_root = Path(__file__).parent.parent + repo_root = Path(__file__).parent.parent.parent env = dict(os.environ) env.update( { @@ -216,7 +216,7 @@ if 'cryptography.hazmat.bindings._rust' in sys.modules: print('PASS: main() update --check path never loaded cryptography._rust') sys.exit(0) """ - repo_root = Path(__file__).parent.parent + repo_root = Path(__file__).parent.parent.parent probe = repo_root / "_test_main_update_crypto_probe.py" probe.write_text(script) try: diff --git a/tests/test_lazy_secrets_import.py b/tests/hermes_cli/test_lazy_secrets_import.py similarity index 97% rename from tests/test_lazy_secrets_import.py rename to tests/hermes_cli/test_lazy_secrets_import.py index 7261c10000..8e3f4b5d7e 100644 --- a/tests/test_lazy_secrets_import.py +++ b/tests/hermes_cli/test_lazy_secrets_import.py @@ -8,7 +8,7 @@ from pathlib import Path def _run_isolated(code: str) -> subprocess.CompletedProcess[str]: """Run a Python snippet in the repo root (not tests/).""" - repo_root = Path(__file__).parent.parent + repo_root = Path(__file__).parent.parent.parent return subprocess.run( [sys.executable, "-c", code], capture_output=True, @@ -78,7 +78,7 @@ sys.exit(0) """Running hermes update --check should NOT load cryptography._rust.""" # Write a small script in the repo root so hermes_cli is importable, # and use a filename that doesn't trigger the live-system guard. - repo_root = Path(__file__).parent.parent + repo_root = Path(__file__).parent.parent.parent script = repo_root / "_test_lazy_secrets_check.py" script.write_text( """ diff --git a/tests/test_list_recent_user_messages_handoffs.py b/tests/hermes_cli/test_list_recent_user_messages_handoffs.py similarity index 100% rename from tests/test_list_recent_user_messages_handoffs.py rename to tests/hermes_cli/test_list_recent_user_messages_handoffs.py diff --git a/tests/cli/test_manual_compress.py b/tests/hermes_cli/test_manual_compress.py similarity index 100% rename from tests/cli/test_manual_compress.py rename to tests/hermes_cli/test_manual_compress.py diff --git a/tests/test_minimax_model_validation.py b/tests/hermes_cli/test_minimax_model_validation.py similarity index 100% rename from tests/test_minimax_model_validation.py rename to tests/hermes_cli/test_minimax_model_validation.py diff --git a/tests/test_minimax_oauth.py b/tests/hermes_cli/test_minimax_oauth.py similarity index 100% rename from tests/test_minimax_oauth.py rename to tests/hermes_cli/test_minimax_oauth.py diff --git a/tests/cli/test_moa_command.py b/tests/hermes_cli/test_moa_command.py similarity index 100% rename from tests/cli/test_moa_command.py rename to tests/hermes_cli/test_moa_command.py diff --git a/tests/hermes_cli/test_model_alias_credentials_83612.py b/tests/hermes_cli/test_model_alias_credentials.py similarity index 100% rename from tests/hermes_cli/test_model_alias_credentials_83612.py rename to tests/hermes_cli/test_model_alias_credentials.py diff --git a/tests/cli/test_model_picker_filter.py b/tests/hermes_cli/test_model_picker_filter.py similarity index 100% rename from tests/cli/test_model_picker_filter.py rename to tests/hermes_cli/test_model_picker_filter.py diff --git a/tests/hermes_cli/test_model_prefix_routing_87189.py b/tests/hermes_cli/test_model_prefix_routing.py similarity index 100% rename from tests/hermes_cli/test_model_prefix_routing_87189.py rename to tests/hermes_cli/test_model_prefix_routing.py diff --git a/tests/cli/test_modify_other_keys_aliases.py b/tests/hermes_cli/test_modify_other_keys_aliases.py similarity index 100% rename from tests/cli/test_modify_other_keys_aliases.py rename to tests/hermes_cli/test_modify_other_keys_aliases.py diff --git a/tests/cli/test_oneshot_resumed_session_persist.py b/tests/hermes_cli/test_oneshot_resumed_session_persist.py similarity index 100% rename from tests/cli/test_oneshot_resumed_session_persist.py rename to tests/hermes_cli/test_oneshot_resumed_session_persist.py diff --git a/tests/cli/test_partial_compress.py b/tests/hermes_cli/test_partial_compress.py similarity index 100% rename from tests/cli/test_partial_compress.py rename to tests/hermes_cli/test_partial_compress.py diff --git a/tests/cli/test_personality_none.py b/tests/hermes_cli/test_personality_none.py similarity index 100% rename from tests/cli/test_personality_none.py rename to tests/hermes_cli/test_personality_none.py diff --git a/tests/test_plugin_compat_notice.py b/tests/hermes_cli/test_plugin_compat_notice.py similarity index 98% rename from tests/test_plugin_compat_notice.py rename to tests/hermes_cli/test_plugin_compat_notice.py index c8574a86c2..f739e51154 100644 --- a/tests/test_plugin_compat_notice.py +++ b/tests/hermes_cli/test_plugin_compat_notice.py @@ -1,6 +1,6 @@ """hermes_cli.plugin_compat: detect plugins on old import paths, tell the user, disable after the date. -Kept with the compat layer (tests/test_compat_manifest_targets.py); deleted with it. +Kept with the compat layer (tests/hermes_cli/test_compat_manifest_targets.py); deleted with it. """ from __future__ import annotations @@ -14,7 +14,7 @@ import pytest from hermes_cli import plugin_compat as pc -ROOT = Path(__file__).resolve().parent.parent +ROOT = Path(__file__).resolve().parent.parent.parent pytestmark = pytest.mark.skipif(not (ROOT / "compat_manifest.json").exists(), reason="compat layer removed") MANIFEST = {"tools.web_tools": {"prefers_gateway": "tools.tool_backend_helpers.prefers_gateway"}, diff --git a/tests/cli/test_prefill_config.py b/tests/hermes_cli/test_prefill_config.py similarity index 100% rename from tests/cli/test_prefill_config.py rename to tests/hermes_cli/test_prefill_config.py diff --git a/tests/cli/test_prepend_note_to_message.py b/tests/hermes_cli/test_prepend_note_to_message.py similarity index 100% rename from tests/cli/test_prepend_note_to_message.py rename to tests/hermes_cli/test_prepend_note_to_message.py diff --git a/tests/cli/test_prompt_stash.py b/tests/hermes_cli/test_prompt_stash.py similarity index 100% rename from tests/cli/test_prompt_stash.py rename to tests/hermes_cli/test_prompt_stash.py diff --git a/tests/cli/test_prompt_stash_cli.py b/tests/hermes_cli/test_prompt_stash_cli.py similarity index 98% rename from tests/cli/test_prompt_stash_cli.py rename to tests/hermes_cli/test_prompt_stash_cli.py index b613fcbd66..55235129e9 100644 --- a/tests/cli/test_prompt_stash_cli.py +++ b/tests/hermes_cli/test_prompt_stash_cli.py @@ -1,6 +1,6 @@ """Tests for the Ctrl+S prompt stash wiring inside HermesCLI. -The state machine itself is covered by tests/cli/test_prompt_stash.py. These +The state machine itself is covered by tests/hermes_cli/test_prompt_stash.py. These tests verify the cli.py side: - HermesCLI.__init__ creates a PromptStash - the layout hook makes room for the stash browse panel @@ -8,7 +8,7 @@ tests verify the cli.py side: - the status-bar indicator appears / disappears with stash contents Follows the prompt_toolkit-stub construction pattern from -tests/cli/test_cli_extension_hooks.py. +tests/hermes_cli/test_cli_extension_hooks.py. """ from __future__ import annotations diff --git a/tests/cli/test_prompt_text_input_thread_safety.py b/tests/hermes_cli/test_prompt_text_input_thread_safety.py similarity index 100% rename from tests/cli/test_prompt_text_input_thread_safety.py rename to tests/hermes_cli/test_prompt_text_input_thread_safety.py diff --git a/tests/test_pty_keepalive_ws.py b/tests/hermes_cli/test_pty_keepalive_ws.py similarity index 100% rename from tests/test_pty_keepalive_ws.py rename to tests/hermes_cli/test_pty_keepalive_ws.py diff --git a/tests/test_pty_session.py b/tests/hermes_cli/test_pty_session.py similarity index 100% rename from tests/test_pty_session.py rename to tests/hermes_cli/test_pty_session.py diff --git a/tests/cli/test_quick_commands.py b/tests/hermes_cli/test_quick_commands.py similarity index 100% rename from tests/cli/test_quick_commands.py rename to tests/hermes_cli/test_quick_commands.py diff --git a/tests/cli/test_quiet_turn_author.py b/tests/hermes_cli/test_quiet_turn_author.py similarity index 100% rename from tests/cli/test_quiet_turn_author.py rename to tests/hermes_cli/test_quiet_turn_author.py diff --git a/tests/cli/test_reasoning_command.py b/tests/hermes_cli/test_reasoning_command.py similarity index 100% rename from tests/cli/test_reasoning_command.py rename to tests/hermes_cli/test_reasoning_command.py diff --git a/tests/hermes_cli/test_regression_16767.py b/tests/hermes_cli/test_regression.py similarity index 98% rename from tests/hermes_cli/test_regression_16767.py rename to tests/hermes_cli/test_regression.py index bbbae4df58..5f0d8f4b40 100644 --- a/tests/hermes_cli/test_regression_16767.py +++ b/tests/hermes_cli/test_regression.py @@ -1,3 +1,5 @@ +"""Regression coverage for issue #16767.""" + import sys import hermes_cli.model_switch as ms diff --git a/tests/test_resource_limits.py b/tests/hermes_cli/test_resource_limits.py similarity index 100% rename from tests/test_resource_limits.py rename to tests/hermes_cli/test_resource_limits.py diff --git a/tests/cli/test_resume_display.py b/tests/hermes_cli/test_resume_display.py similarity index 100% rename from tests/cli/test_resume_display.py rename to tests/hermes_cli/test_resume_display.py diff --git a/tests/cli/test_resume_model_restore.py b/tests/hermes_cli/test_resume_model_restore.py similarity index 100% rename from tests/cli/test_resume_model_restore.py rename to tests/hermes_cli/test_resume_model_restore.py diff --git a/tests/cli/test_resume_quiet_stderr.py b/tests/hermes_cli/test_resume_quiet_stderr.py similarity index 100% rename from tests/cli/test_resume_quiet_stderr.py rename to tests/hermes_cli/test_resume_quiet_stderr.py diff --git a/tests/cli/test_save_conversation_location.py b/tests/hermes_cli/test_save_conversation_location.py similarity index 100% rename from tests/cli/test_save_conversation_location.py rename to tests/hermes_cli/test_save_conversation_location.py diff --git a/tests/cli/test_session_boundary_hooks.py b/tests/hermes_cli/test_session_boundary_hooks.py similarity index 100% rename from tests/cli/test_session_boundary_hooks.py rename to tests/hermes_cli/test_session_boundary_hooks.py diff --git a/tests/test_session_detail_malformed_db.py b/tests/hermes_cli/test_session_detail_malformed_db.py similarity index 100% rename from tests/test_session_detail_malformed_db.py rename to tests/hermes_cli/test_session_detail_malformed_db.py diff --git a/tests/test_session_export_batch.py b/tests/hermes_cli/test_session_export_batch.py similarity index 100% rename from tests/test_session_export_batch.py rename to tests/hermes_cli/test_session_export_batch.py diff --git a/tests/test_session_search_context_batch.py b/tests/hermes_cli/test_session_search_context_batch.py similarity index 100% rename from tests/test_session_search_context_batch.py rename to tests/hermes_cli/test_session_search_context_batch.py diff --git a/tests/test_session_skill_previews.py b/tests/hermes_cli/test_session_skill_previews.py similarity index 100% rename from tests/test_session_skill_previews.py rename to tests/hermes_cli/test_session_skill_previews.py diff --git a/tests/test_session_system_prompt_dedup.py b/tests/hermes_cli/test_session_system_prompt_dedup.py similarity index 100% rename from tests/test_session_system_prompt_dedup.py rename to tests/hermes_cli/test_session_system_prompt_dedup.py diff --git a/tests/test_session_vacuum_config.py b/tests/hermes_cli/test_session_vacuum_config.py similarity index 98% rename from tests/test_session_vacuum_config.py rename to tests/hermes_cli/test_session_vacuum_config.py index 43adfdacba..08f9e175bd 100644 --- a/tests/test_session_vacuum_config.py +++ b/tests/hermes_cli/test_session_vacuum_config.py @@ -74,7 +74,7 @@ def test_shipped_template_does_not_pin_sessions_keys(): so code defaults (and future flips) apply.""" import yaml - template = Path(__file__).resolve().parents[1] / "cli-config.yaml.example" + template = Path(__file__).resolve().parents[2] / "cli-config.yaml.example" data = yaml.safe_load(template.read_text(encoding="utf-8")) or {} assert "sessions" not in data diff --git a/tests/cli/test_show_config_credential.py b/tests/hermes_cli/test_show_config_credential.py similarity index 100% rename from tests/cli/test_show_config_credential.py rename to tests/hermes_cli/test_show_config_credential.py diff --git a/tests/cli/test_single_query_clarify.py b/tests/hermes_cli/test_single_query_clarify.py similarity index 100% rename from tests/cli/test_single_query_clarify.py rename to tests/hermes_cli/test_single_query_clarify.py diff --git a/tests/cli/test_single_query_session_finalize.py b/tests/hermes_cli/test_single_query_session_finalize.py similarity index 100% rename from tests/cli/test_single_query_session_finalize.py rename to tests/hermes_cli/test_single_query_session_finalize.py diff --git a/tests/cli/test_slash_command_interrupt.py b/tests/hermes_cli/test_slash_command_interrupt.py similarity index 100% rename from tests/cli/test_slash_command_interrupt.py rename to tests/hermes_cli/test_slash_command_interrupt.py diff --git a/tests/cli/test_slash_confirm_windows.py b/tests/hermes_cli/test_slash_confirm_windows.py similarity index 100% rename from tests/cli/test_slash_confirm_windows.py rename to tests/hermes_cli/test_slash_confirm_windows.py diff --git a/tests/cli/test_slash_dispatch_table.py b/tests/hermes_cli/test_slash_dispatch_table.py similarity index 100% rename from tests/cli/test_slash_dispatch_table.py rename to tests/hermes_cli/test_slash_dispatch_table.py diff --git a/tests/cli/test_slash_undo_title_robustness.py b/tests/hermes_cli/test_slash_undo_title_robustness.py similarity index 100% rename from tests/cli/test_slash_undo_title_robustness.py rename to tests/hermes_cli/test_slash_undo_title_robustness.py diff --git a/tests/hermes_cli/test_startup_model_routing_87189.py b/tests/hermes_cli/test_startup_model_routing.py similarity index 100% rename from tests/hermes_cli/test_startup_model_routing_87189.py rename to tests/hermes_cli/test_startup_model_routing.py diff --git a/tests/cli/test_status_bar_git_branch.py b/tests/hermes_cli/test_status_bar_git_branch.py similarity index 100% rename from tests/cli/test_status_bar_git_branch.py rename to tests/hermes_cli/test_status_bar_git_branch.py diff --git a/tests/cli/test_status_lines_held_during_stream_box.py b/tests/hermes_cli/test_status_lines_held_during_stream_box.py similarity index 100% rename from tests/cli/test_status_lines_held_during_stream_box.py rename to tests/hermes_cli/test_status_lines_held_during_stream_box.py diff --git a/tests/cli/test_steer_inline_repaint_34569.py b/tests/hermes_cli/test_steer_inline_repaint.py similarity index 100% rename from tests/cli/test_steer_inline_repaint_34569.py rename to tests/hermes_cli/test_steer_inline_repaint.py diff --git a/tests/cli/test_stream_delta_think_tag.py b/tests/hermes_cli/test_stream_delta_think_tag.py similarity index 100% rename from tests/cli/test_stream_delta_think_tag.py rename to tests/hermes_cli/test_stream_delta_think_tag.py diff --git a/tests/cli/test_stream_flush_left.py b/tests/hermes_cli/test_stream_flush_left.py similarity index 100% rename from tests/cli/test_stream_flush_left.py rename to tests/hermes_cli/test_stream_flush_left.py diff --git a/tests/cli/test_stream_partial_line_flush.py b/tests/hermes_cli/test_stream_partial_line_flush.py similarity index 100% rename from tests/cli/test_stream_partial_line_flush.py rename to tests/hermes_cli/test_stream_partial_line_flush.py diff --git a/tests/cli/test_subagent_dock_collapse.py b/tests/hermes_cli/test_subagent_dock_collapse.py similarity index 100% rename from tests/cli/test_subagent_dock_collapse.py rename to tests/hermes_cli/test_subagent_dock_collapse.py diff --git a/tests/cli/test_subagent_dock_surface.py b/tests/hermes_cli/test_subagent_dock_surface.py similarity index 100% rename from tests/cli/test_subagent_dock_surface.py rename to tests/hermes_cli/test_subagent_dock_surface.py diff --git a/tests/cli/test_subagent_monitor.py b/tests/hermes_cli/test_subagent_monitor.py similarity index 100% rename from tests/cli/test_subagent_monitor.py rename to tests/hermes_cli/test_subagent_monitor.py diff --git a/tests/cli/test_subagent_monitor_prompts.py b/tests/hermes_cli/test_subagent_monitor_prompts.py similarity index 100% rename from tests/cli/test_subagent_monitor_prompts.py rename to tests/hermes_cli/test_subagent_monitor_prompts.py diff --git a/tests/cli/test_subagent_monitor_resize.py b/tests/hermes_cli/test_subagent_monitor_resize.py similarity index 100% rename from tests/cli/test_subagent_monitor_resize.py rename to tests/hermes_cli/test_subagent_monitor_resize.py diff --git a/tests/cli/test_subagent_monitor_ui.py b/tests/hermes_cli/test_subagent_monitor_ui.py similarity index 100% rename from tests/cli/test_subagent_monitor_ui.py rename to tests/hermes_cli/test_subagent_monitor_ui.py diff --git a/tests/cli/test_subagent_notification_display.py b/tests/hermes_cli/test_subagent_notification_display.py similarity index 100% rename from tests/cli/test_subagent_notification_display.py rename to tests/hermes_cli/test_subagent_notification_display.py diff --git a/tests/cli/test_subagent_shortcut.py b/tests/hermes_cli/test_subagent_shortcut.py similarity index 100% rename from tests/cli/test_subagent_shortcut.py rename to tests/hermes_cli/test_subagent_shortcut.py diff --git a/tests/cli/test_terminal_interrupt_recovery.py b/tests/hermes_cli/test_terminal_interrupt_recovery.py similarity index 100% rename from tests/cli/test_terminal_interrupt_recovery.py rename to tests/hermes_cli/test_terminal_interrupt_recovery.py diff --git a/tests/hermes_cli/test_terminal_io_broken_81521.py b/tests/hermes_cli/test_terminal_io_broken.py similarity index 100% rename from tests/hermes_cli/test_terminal_io_broken_81521.py rename to tests/hermes_cli/test_terminal_io_broken.py diff --git a/tests/cli/test_termios_drift_heal.py b/tests/hermes_cli/test_termios_drift_heal.py similarity index 100% rename from tests/cli/test_termios_drift_heal.py rename to tests/hermes_cli/test_termios_drift_heal.py diff --git a/tests/cli/test_tool_gen_start_dedupe.py b/tests/hermes_cli/test_tool_gen_start_dedupe.py similarity index 100% rename from tests/cli/test_tool_gen_start_dedupe.py rename to tests/hermes_cli/test_tool_gen_start_dedupe.py diff --git a/tests/cli/test_tool_progress_scrollback.py b/tests/hermes_cli/test_tool_progress_scrollback.py similarity index 100% rename from tests/cli/test_tool_progress_scrollback.py rename to tests/hermes_cli/test_tool_progress_scrollback.py diff --git a/tests/cli/test_transformed_stream_output.py b/tests/hermes_cli/test_transformed_stream_output.py similarity index 100% rename from tests/cli/test_transformed_stream_output.py rename to tests/hermes_cli/test_transformed_stream_output.py diff --git a/tests/cli/test_tui_rapid_enter_paste.py b/tests/hermes_cli/test_tui_rapid_enter_paste.py similarity index 100% rename from tests/cli/test_tui_rapid_enter_paste.py rename to tests/hermes_cli/test_tui_rapid_enter_paste.py diff --git a/tests/cli/test_tui_terminal_reset_on_exit.py b/tests/hermes_cli/test_tui_terminal_reset_on_exit.py similarity index 100% rename from tests/cli/test_tui_terminal_reset_on_exit.py rename to tests/hermes_cli/test_tui_terminal_reset_on_exit.py diff --git a/tests/cli/test_update_command.py b/tests/hermes_cli/test_update_command.py similarity index 100% rename from tests/cli/test_update_command.py rename to tests/hermes_cli/test_update_command.py diff --git a/tests/cli/test_version_command.py b/tests/hermes_cli/test_version_command.py similarity index 100% rename from tests/cli/test_version_command.py rename to tests/hermes_cli/test_version_command.py diff --git a/tests/cli/test_vim_mode_command.py b/tests/hermes_cli/test_vim_mode_command.py similarity index 100% rename from tests/cli/test_vim_mode_command.py rename to tests/hermes_cli/test_vim_mode_command.py diff --git a/tests/test_web_server_sessiondb_eventloop.py b/tests/hermes_cli/test_web_server_sessiondb_eventloop.py similarity index 100% rename from tests/test_web_server_sessiondb_eventloop.py rename to tests/hermes_cli/test_web_server_sessiondb_eventloop.py diff --git a/tests/test_web_server_status_topology_cache.py b/tests/hermes_cli/test_web_server_status_topology_cache.py similarity index 100% rename from tests/test_web_server_status_topology_cache.py rename to tests/hermes_cli/test_web_server_status_topology_cache.py diff --git a/tests/test_web_server.py b/tests/hermes_cli/test_web_server_ws_ping.py similarity index 100% rename from tests/test_web_server.py rename to tests/hermes_cli/test_web_server_ws_ping.py diff --git a/tests/hermes_cli/test_windows_gateway_job_teardown_48820.py b/tests/hermes_cli/test_windows_gateway_job_teardown.py similarity index 100% rename from tests/hermes_cli/test_windows_gateway_job_teardown_48820.py rename to tests/hermes_cli/test_windows_gateway_job_teardown.py diff --git a/tests/cli/test_worktree.py b/tests/hermes_cli/test_worktree.py similarity index 100% rename from tests/cli/test_worktree.py rename to tests/hermes_cli/test_worktree.py diff --git a/tests/cli/test_worktree_pushed_tier.py b/tests/hermes_cli/test_worktree_pushed_tier.py similarity index 100% rename from tests/cli/test_worktree_pushed_tier.py rename to tests/hermes_cli/test_worktree_pushed_tier.py diff --git a/tests/cli/test_worktree_security.py b/tests/hermes_cli/test_worktree_security.py similarity index 100% rename from tests/cli/test_worktree_security.py rename to tests/hermes_cli/test_worktree_security.py diff --git a/tests/cli/test_worktree_selfheal.py b/tests/hermes_cli/test_worktree_selfheal.py similarity index 100% rename from tests/cli/test_worktree_selfheal.py rename to tests/hermes_cli/test_worktree_selfheal.py diff --git a/tests/cli/test_worktree_sync_base.py b/tests/hermes_cli/test_worktree_sync_base.py similarity index 100% rename from tests/cli/test_worktree_sync_base.py rename to tests/hermes_cli/test_worktree_sync_base.py diff --git a/tests/state/test_append_message_after_close_94736.py b/tests/hermes_state/test_append_message_after_close.py similarity index 100% rename from tests/state/test_append_message_after_close_94736.py rename to tests/hermes_state/test_append_message_after_close.py diff --git a/tests/hermes_state/test_88197_automatic_ended_stamp.py b/tests/hermes_state/test_automatic_ended_stamp.py similarity index 100% rename from tests/hermes_state/test_88197_automatic_ended_stamp.py rename to tests/hermes_state/test_automatic_ended_stamp.py diff --git a/tests/test_background_review_session_isolation.py b/tests/hermes_state/test_background_review_session_isolation.py similarity index 100% rename from tests/test_background_review_session_isolation.py rename to tests/hermes_state/test_background_review_session_isolation.py diff --git a/tests/state/test_compression_lease_refresh_before_publish.py b/tests/hermes_state/test_compression_lease_refresh_before_publish.py similarity index 100% rename from tests/state/test_compression_lease_refresh_before_publish.py rename to tests/hermes_state/test_compression_lease_refresh_before_publish.py diff --git a/tests/state/test_compression_lineage_guard.py b/tests/hermes_state/test_compression_lineage_guard.py similarity index 100% rename from tests/state/test_compression_lineage_guard.py rename to tests/hermes_state/test_compression_lineage_guard.py diff --git a/tests/test_compression_watermark_commit.py b/tests/hermes_state/test_compression_watermark_commit.py similarity index 100% rename from tests/test_compression_watermark_commit.py rename to tests/hermes_state/test_compression_watermark_commit.py diff --git a/tests/test_conftest_wal_gate.py b/tests/hermes_state/test_conftest_wal_gate.py similarity index 100% rename from tests/test_conftest_wal_gate.py rename to tests/hermes_state/test_conftest_wal_gate.py diff --git a/tests/state/test_dedupe_migration_contention.py b/tests/hermes_state/test_dedupe_migration_contention.py similarity index 100% rename from tests/state/test_dedupe_migration_contention.py rename to tests/hermes_state/test_dedupe_migration_contention.py diff --git a/tests/test_delegate_cascade_49148.py b/tests/hermes_state/test_delegate_cascade.py similarity index 100% rename from tests/test_delegate_cascade_49148.py rename to tests/hermes_state/test_delegate_cascade.py diff --git a/tests/state/test_disk_full_error.py b/tests/hermes_state/test_disk_full_error.py similarity index 100% rename from tests/state/test_disk_full_error.py rename to tests/hermes_state/test_disk_full_error.py diff --git a/tests/hermes_state/test_empty_sweep_archived_transcript_95868.py b/tests/hermes_state/test_empty_sweep_archived_transcript.py similarity index 100% rename from tests/hermes_state/test_empty_sweep_archived_transcript_95868.py rename to tests/hermes_state/test_empty_sweep_archived_transcript.py diff --git a/tests/test_fts_cjk_bigram.py b/tests/hermes_state/test_fts_cjk_bigram.py similarity index 99% rename from tests/test_fts_cjk_bigram.py rename to tests/hermes_state/test_fts_cjk_bigram.py index 19c20e6460..8bcfbc8524 100644 --- a/tests/test_fts_cjk_bigram.py +++ b/tests/hermes_state/test_fts_cjk_bigram.py @@ -14,7 +14,7 @@ import pytest from hermes_state import SessionDB from hermes_state_common import FTS_CJK_STALE_KEY -REPO = Path(__file__).resolve().parent.parent +REPO = Path(__file__).resolve().parent.parent.parent SRC = REPO / "native" / "fts5_cjk" / "fts5_cjk.c" VENDOR = REPO / "native" / "fts5_cjk" / "vendor" diff --git a/tests/state/test_fts_fresh_bootstrap_admission.py b/tests/hermes_state/test_fts_fresh_bootstrap_admission.py similarity index 100% rename from tests/state/test_fts_fresh_bootstrap_admission.py rename to tests/hermes_state/test_fts_fresh_bootstrap_admission.py diff --git a/tests/state/test_fts_holder_instance_scope.py b/tests/hermes_state/test_fts_holder_instance_scope.py similarity index 100% rename from tests/state/test_fts_holder_instance_scope.py rename to tests/hermes_state/test_fts_holder_instance_scope.py diff --git a/tests/state/test_fts_index_fail_open.py b/tests/hermes_state/test_fts_index_fail_open.py similarity index 100% rename from tests/state/test_fts_index_fail_open.py rename to tests/hermes_state/test_fts_index_fail_open.py diff --git a/tests/state/test_fts_orphan_shadow_repair.py b/tests/hermes_state/test_fts_orphan_shadow_repair.py similarity index 100% rename from tests/state/test_fts_orphan_shadow_repair.py rename to tests/hermes_state/test_fts_orphan_shadow_repair.py diff --git a/tests/state/test_fts_rebuild_admission.py b/tests/hermes_state/test_fts_rebuild_admission.py similarity index 100% rename from tests/state/test_fts_rebuild_admission.py rename to tests/hermes_state/test_fts_rebuild_admission.py diff --git a/tests/state/test_fts_runtime_rebuild.py b/tests/hermes_state/test_fts_runtime_rebuild.py similarity index 100% rename from tests/state/test_fts_runtime_rebuild.py rename to tests/hermes_state/test_fts_runtime_rebuild.py diff --git a/tests/test_fts_tool_write_bounds.py b/tests/hermes_state/test_fts_tool_write_bounds.py similarity index 100% rename from tests/test_fts_tool_write_bounds.py rename to tests/hermes_state/test_fts_tool_write_bounds.py diff --git a/tests/state/test_fts_trigram_cron_exclusion.py b/tests/hermes_state/test_fts_trigram_cron_exclusion.py similarity index 100% rename from tests/state/test_fts_trigram_cron_exclusion.py rename to tests/hermes_state/test_fts_trigram_cron_exclusion.py diff --git a/tests/state/test_fts_trigram_subagent_exclusion.py b/tests/hermes_state/test_fts_trigram_subagent_exclusion.py similarity index 100% rename from tests/state/test_fts_trigram_subagent_exclusion.py rename to tests/hermes_state/test_fts_trigram_subagent_exclusion.py diff --git a/tests/test_fts_update_of_narrowing.py b/tests/hermes_state/test_fts_update_of_narrowing.py similarity index 100% rename from tests/test_fts_update_of_narrowing.py rename to tests/hermes_state/test_fts_update_of_narrowing.py diff --git a/tests/test_guest_durability_barriers.py b/tests/hermes_state/test_guest_durability_barriers.py similarity index 100% rename from tests/test_guest_durability_barriers.py rename to tests/hermes_state/test_guest_durability_barriers.py diff --git a/tests/test_hermes_state.py b/tests/hermes_state/test_hermes_state.py similarity index 100% rename from tests/test_hermes_state.py rename to tests/hermes_state/test_hermes_state.py diff --git a/tests/test_hermes_state_compression_busy_retry.py b/tests/hermes_state/test_hermes_state_compression_busy_retry.py similarity index 100% rename from tests/test_hermes_state_compression_busy_retry.py rename to tests/hermes_state/test_hermes_state_compression_busy_retry.py diff --git a/tests/test_hermes_state_compression_locks.py b/tests/hermes_state/test_hermes_state_compression_locks.py similarity index 100% rename from tests/test_hermes_state_compression_locks.py rename to tests/hermes_state/test_hermes_state_compression_locks.py diff --git a/tests/test_hermes_state_conn_lock_audit.py b/tests/hermes_state/test_hermes_state_conn_lock_audit.py similarity index 98% rename from tests/test_hermes_state_conn_lock_audit.py rename to tests/hermes_state/test_hermes_state_conn_lock_audit.py index f8669924fd..1b05f788b7 100644 --- a/tests/test_hermes_state_conn_lock_audit.py +++ b/tests/hermes_state/test_hermes_state_conn_lock_audit.py @@ -34,7 +34,7 @@ _ALLOWED_UNLOCKED_FNS = frozenset({ def _repo_root() -> Path: - return Path(__file__).resolve().parents[1] + return Path(__file__).resolve().parents[2] def _nearest_enclosing_fn(tree: ast.AST) -> dict: diff --git a/tests/test_hermes_state_ids.py b/tests/hermes_state/test_hermes_state_ids.py similarity index 100% rename from tests/test_hermes_state_ids.py rename to tests/hermes_state/test_hermes_state_ids.py diff --git a/tests/test_hermes_state_readonly_preflight.py b/tests/hermes_state/test_hermes_state_readonly_preflight.py similarity index 100% rename from tests/test_hermes_state_readonly_preflight.py rename to tests/hermes_state/test_hermes_state_readonly_preflight.py diff --git a/tests/test_hermes_state_wal_fallback.py b/tests/hermes_state/test_hermes_state_wal_fallback.py similarity index 100% rename from tests/test_hermes_state_wal_fallback.py rename to tests/hermes_state/test_hermes_state_wal_fallback.py diff --git a/tests/test_journal_mode_config.py b/tests/hermes_state/test_journal_mode_config.py similarity index 100% rename from tests/test_journal_mode_config.py rename to tests/hermes_state/test_journal_mode_config.py diff --git a/tests/test_journal_mode_upgrade_warning.py b/tests/hermes_state/test_journal_mode_upgrade_warning.py similarity index 100% rename from tests/test_journal_mode_upgrade_warning.py rename to tests/hermes_state/test_journal_mode_upgrade_warning.py diff --git a/tests/state/test_no_locked_readers_gate.py b/tests/hermes_state/test_no_locked_readers_gate.py similarity index 100% rename from tests/state/test_no_locked_readers_gate.py rename to tests/hermes_state/test_no_locked_readers_gate.py diff --git a/tests/state/test_no_more_rows_retry.py b/tests/hermes_state/test_no_more_rows_retry.py similarity index 100% rename from tests/state/test_no_more_rows_retry.py rename to tests/hermes_state/test_no_more_rows_retry.py diff --git a/tests/hermes_state/test_94895_orphan_sweep_cross_backend.py b/tests/hermes_state/test_orphan_sweep_cross_backend.py similarity index 100% rename from tests/hermes_state/test_94895_orphan_sweep_cross_backend.py rename to tests/hermes_state/test_orphan_sweep_cross_backend.py diff --git a/tests/test_98924_readonly_fts_decode_error.py b/tests/hermes_state/test_readonly_fts_decode_error.py similarity index 100% rename from tests/test_98924_readonly_fts_decode_error.py rename to tests/hermes_state/test_readonly_fts_decode_error.py diff --git a/tests/test_schema_read_probe.py b/tests/hermes_state/test_schema_read_probe.py similarity index 100% rename from tests/test_schema_read_probe.py rename to tests/hermes_state/test_schema_read_probe.py diff --git a/tests/test_search_slow_query_log.py b/tests/hermes_state/test_search_slow_query_log.py similarity index 100% rename from tests/test_search_slow_query_log.py rename to tests/hermes_state/test_search_slow_query_log.py diff --git a/tests/test_session_db_context_manager.py b/tests/hermes_state/test_session_db_context_manager.py similarity index 100% rename from tests/test_session_db_context_manager.py rename to tests/hermes_state/test_session_db_context_manager.py diff --git a/tests/test_session_db_profile_stamp.py b/tests/hermes_state/test_session_db_profile_stamp.py similarity index 100% rename from tests/test_session_db_profile_stamp.py rename to tests/hermes_state/test_session_db_profile_stamp.py diff --git a/tests/test_session_db_read_conn_pool.py b/tests/hermes_state/test_session_db_read_conn_pool.py similarity index 100% rename from tests/test_session_db_read_conn_pool.py rename to tests/hermes_state/test_session_db_read_conn_pool.py diff --git a/tests/test_session_db_read_path_split.py b/tests/hermes_state/test_session_db_read_path_split.py similarity index 100% rename from tests/test_session_db_read_path_split.py rename to tests/hermes_state/test_session_db_read_path_split.py diff --git a/tests/test_session_delete_profile_isolation.py b/tests/hermes_state/test_session_delete_profile_isolation.py similarity index 100% rename from tests/test_session_delete_profile_isolation.py rename to tests/hermes_state/test_session_delete_profile_isolation.py diff --git a/tests/state/test_session_git_metadata_generation.py b/tests/hermes_state/test_session_git_metadata_generation.py similarity index 100% rename from tests/state/test_session_git_metadata_generation.py rename to tests/hermes_state/test_session_git_metadata_generation.py diff --git a/tests/state/test_session_model_usage_pk_heal.py b/tests/hermes_state/test_session_model_usage_pk_heal.py similarity index 100% rename from tests/state/test_session_model_usage_pk_heal.py rename to tests/hermes_state/test_session_model_usage_pk_heal.py diff --git a/tests/state/test_session_turn_lease.py b/tests/hermes_state/test_session_turn_lease.py similarity index 100% rename from tests/state/test_session_turn_lease.py rename to tests/hermes_state/test_session_turn_lease.py diff --git a/tests/test_session_workspace_binding.py b/tests/hermes_state/test_session_workspace_binding.py similarity index 100% rename from tests/test_session_workspace_binding.py rename to tests/hermes_state/test_session_workspace_binding.py diff --git a/tests/test_sqlite_lock_safe_inspection.py b/tests/hermes_state/test_sqlite_lock_safe_inspection.py similarity index 100% rename from tests/test_sqlite_lock_safe_inspection.py rename to tests/hermes_state/test_sqlite_lock_safe_inspection.py diff --git a/tests/test_sqlite_wal_reset_gate.py b/tests/hermes_state/test_sqlite_wal_reset_gate.py similarity index 100% rename from tests/test_sqlite_wal_reset_gate.py rename to tests/hermes_state/test_sqlite_wal_reset_gate.py diff --git a/tests/test_stale_tool_call_marker_session_repair.py b/tests/hermes_state/test_stale_tool_call_marker_session_repair.py similarity index 100% rename from tests/test_stale_tool_call_marker_session_repair.py rename to tests/hermes_state/test_stale_tool_call_marker_session_repair.py diff --git a/tests/test_state_db_fts_segment_collision_probe.py b/tests/hermes_state/test_state_db_fts_segment_collision_probe.py similarity index 100% rename from tests/test_state_db_fts_segment_collision_probe.py rename to tests/hermes_state/test_state_db_fts_segment_collision_probe.py diff --git a/tests/state/test_state_db_holders.py b/tests/hermes_state/test_state_db_holders.py similarity index 100% rename from tests/state/test_state_db_holders.py rename to tests/hermes_state/test_state_db_holders.py diff --git a/tests/state/test_state_db_lock_fail_closed.py b/tests/hermes_state/test_state_db_lock_fail_closed.py similarity index 100% rename from tests/state/test_state_db_lock_fail_closed.py rename to tests/hermes_state/test_state_db_lock_fail_closed.py diff --git a/tests/test_state_db_malformed_repair.py b/tests/hermes_state/test_state_db_malformed_repair.py similarity index 100% rename from tests/test_state_db_malformed_repair.py rename to tests/hermes_state/test_state_db_malformed_repair.py diff --git a/tests/test_state_db_notadb_fail_closed.py b/tests/hermes_state/test_state_db_notadb_fail_closed.py similarity index 100% rename from tests/test_state_db_notadb_fail_closed.py rename to tests/hermes_state/test_state_db_notadb_fail_closed.py diff --git a/tests/test_state_db_repair_live_writer_guard.py b/tests/hermes_state/test_state_db_repair_live_writer_guard.py similarity index 100% rename from tests/test_state_db_repair_live_writer_guard.py rename to tests/hermes_state/test_state_db_repair_live_writer_guard.py diff --git a/tests/test_state_db_repair_loop_cap.py b/tests/hermes_state/test_state_db_repair_loop_cap.py similarity index 100% rename from tests/test_state_db_repair_loop_cap.py rename to tests/hermes_state/test_state_db_repair_loop_cap.py diff --git a/tests/test_state_db_repair_loop_mtime.py b/tests/hermes_state/test_state_db_repair_loop_mtime.py similarity index 100% rename from tests/test_state_db_repair_loop_mtime.py rename to tests/hermes_state/test_state_db_repair_loop_mtime.py diff --git a/tests/test_state_db_repair_non_destructive.py b/tests/hermes_state/test_state_db_repair_non_destructive.py similarity index 100% rename from tests/test_state_db_repair_non_destructive.py rename to tests/hermes_state/test_state_db_repair_non_destructive.py diff --git a/tests/test_state_db_stats.py b/tests/hermes_state/test_state_db_stats.py similarity index 100% rename from tests/test_state_db_stats.py rename to tests/hermes_state/test_state_db_stats.py diff --git a/tests/state/test_state_db_wal_unlink_race.py b/tests/hermes_state/test_state_db_wal_unlink_race.py similarity index 100% rename from tests/state/test_state_db_wal_unlink_race.py rename to tests/hermes_state/test_state_db_wal_unlink_race.py diff --git a/tests/test_state_db_write_durability.py b/tests/hermes_state/test_state_db_write_durability.py similarity index 100% rename from tests/test_state_db_write_durability.py rename to tests/hermes_state/test_state_db_write_durability.py diff --git a/tests/test_state_synchronous_pragma.py b/tests/hermes_state/test_state_synchronous_pragma.py similarity index 98% rename from tests/test_state_synchronous_pragma.py rename to tests/hermes_state/test_state_synchronous_pragma.py index ce34c8d025..e6ec50ecf6 100644 --- a/tests/test_state_synchronous_pragma.py +++ b/tests/hermes_state/test_state_synchronous_pragma.py @@ -222,7 +222,7 @@ def test_example_config_documents_the_key(): """An unpinnable durability level is the bug; docs are half the fix.""" from pathlib import Path - text = Path(__file__).resolve().parents[1] / "cli-config.yaml.example" + text = Path(__file__).resolve().parents[2] / "cli-config.yaml.example" body = text.read_text(encoding="utf-8") assert "synchronous: FULL" in body @@ -239,7 +239,7 @@ def test_example_config_calls_the_darwin_rule_a_floor_not_a_pin(): from pathlib import Path body = ( - Path(__file__).resolve().parents[1] / "cli-config.yaml.example" + Path(__file__).resolve().parents[2] / "cli-config.yaml.example" ).read_text(encoding="utf-8") assert "floor, not a pin" in body assert "EXTRA is honored" in body diff --git a/tests/hermes_state/test_86366_tail_rewind_semantics.py b/tests/hermes_state/test_tail_rewind_semantics.py similarity index 100% rename from tests/hermes_state/test_86366_tail_rewind_semantics.py rename to tests/hermes_state/test_tail_rewind_semantics.py diff --git a/tests/test_wal_checkpoint_strategy.py b/tests/hermes_state/test_wal_checkpoint_strategy.py similarity index 100% rename from tests/test_wal_checkpoint_strategy.py rename to tests/hermes_state/test_wal_checkpoint_strategy.py diff --git a/tests/hermes_state/test_86366_watermark_interplay.py b/tests/hermes_state/test_watermark_interplay.py similarity index 100% rename from tests/hermes_state/test_86366_watermark_interplay.py rename to tests/hermes_state/test_watermark_interplay.py diff --git a/tests/state/test_write_lock_patience.py b/tests/hermes_state/test_write_lock_patience.py similarity index 100% rename from tests/state/test_write_lock_patience.py rename to tests/hermes_state/test_write_lock_patience.py diff --git a/tests/state/test_writer_conn_thread_safety.py b/tests/hermes_state/test_writer_conn_thread_safety.py similarity index 100% rename from tests/state/test_writer_conn_thread_safety.py rename to tests/hermes_state/test_writer_conn_thread_safety.py diff --git a/tests/test_zeroed_state_db.py b/tests/hermes_state/test_zeroed_state_db.py similarity index 100% rename from tests/test_zeroed_state_db.py rename to tests/hermes_state/test_zeroed_state_db.py diff --git a/tests/perf_guards/test_pattern_b_scaling.py b/tests/perf_guards/test_pattern_b_scaling.py index 7d3dead17e..fce2d94c5a 100644 --- a/tests/perf_guards/test_pattern_b_scaling.py +++ b/tests/perf_guards/test_pattern_b_scaling.py @@ -127,7 +127,7 @@ class TestListSessionsRichQueryBound: """Rows from ``list_sessions_rich`` plus the writer-conn statement count. Uses the house trace-callback idiom (``statements.append`` — see - tests/test_hermes_state.py) so failures can dump the captured SQL. + tests/hermes_state/test_hermes_state.py) so failures can dump the captured SQL. """ statements: list[str] = [] db._conn.set_trace_callback(statements.append) diff --git a/tests/plugins/model_providers/test_deepseek_profile.py b/tests/plugins/model_providers/test_deepseek_profile.py index 90788b00a0..d56d68cb8d 100644 --- a/tests/plugins/model_providers/test_deepseek_profile.py +++ b/tests/plugins/model_providers/test_deepseek_profile.py @@ -141,7 +141,7 @@ class TestDeepSeekModelGating: class TestDeepSeekFullKwargsIntegration: """End-to-end: the transport's full kwargs match DeepSeek's live wire format. - The live test harness in ``tests/run_agent/test_deepseek_v4_thinking_live.py`` + The live test harness in ``tests/agent/test_deepseek_v4_thinking_live.py`` sends ``{"reasoning_effort": "high", "extra_body": {"thinking": {"type": "enabled"}}}``. Confirm the transport produces that exact shape when wired through the registered DeepSeek profile. diff --git a/tests/test_honcho_client_concurrency.py b/tests/plugins/test_honcho_client_concurrency.py similarity index 100% rename from tests/test_honcho_client_concurrency.py rename to tests/plugins/test_honcho_client_concurrency.py diff --git a/tests/test_honcho_client_config.py b/tests/plugins/test_honcho_client_config.py similarity index 100% rename from tests/test_honcho_client_config.py rename to tests/plugins/test_honcho_client_config.py diff --git a/tests/test_honcho_session_context.py b/tests/plugins/test_honcho_session_context.py similarity index 100% rename from tests/test_honcho_session_context.py rename to tests/plugins/test_honcho_session_context.py diff --git a/tests/test_honcho_startup_fail_open.py b/tests/plugins/test_honcho_startup_fail_open.py similarity index 100% rename from tests/test_honcho_startup_fail_open.py rename to tests/plugins/test_honcho_startup_fail_open.py diff --git a/tests/test_plugin_storage.py b/tests/plugins/test_plugin_storage.py similarity index 100% rename from tests/test_plugin_storage.py rename to tests/plugins/test_plugin_storage.py diff --git a/tests/test_plugin_utils.py b/tests/plugins/test_plugin_utils.py similarity index 100% rename from tests/test_plugin_utils.py rename to tests/plugins/test_plugin_utils.py diff --git a/tests/test_plugins_manage_profile_scope.py b/tests/plugins/test_plugins_manage_profile_scope.py similarity index 100% rename from tests/test_plugins_manage_profile_scope.py rename to tests/plugins/test_plugins_manage_profile_scope.py diff --git a/tests/test_secret_scope_plugin_families.py b/tests/plugins/test_secret_scope_plugin_families.py similarity index 100% rename from tests/test_secret_scope_plugin_families.py rename to tests/plugins/test_secret_scope_plugin_families.py diff --git a/tests/test_telegram_polling_progress_ptb.py b/tests/plugins/test_telegram_polling_progress_ptb.py similarity index 100% rename from tests/test_telegram_polling_progress_ptb.py rename to tests/plugins/test_telegram_polling_progress_ptb.py diff --git a/tests/test_transform_api_error_classification_hook.py b/tests/plugins/test_transform_api_error_classification_hook.py similarity index 100% rename from tests/test_transform_api_error_classification_hook.py rename to tests/plugins/test_transform_api_error_classification_hook.py diff --git a/tests/test_transform_llm_output_hook.py b/tests/plugins/test_transform_llm_output_hook.py similarity index 100% rename from tests/test_transform_llm_output_hook.py rename to tests/plugins/test_transform_llm_output_hook.py diff --git a/tests/test_transform_tool_result_hook.py b/tests/plugins/test_transform_tool_result_hook.py similarity index 100% rename from tests/test_transform_tool_result_hook.py rename to tests/plugins/test_transform_tool_result_hook.py diff --git a/tests/run_agent/__init__.py b/tests/run_agent/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/test_desktop_update_linux_gate.py b/tests/scripts/desktop_update/test_desktop_update_linux_gate.py similarity index 95% rename from tests/test_desktop_update_linux_gate.py rename to tests/scripts/desktop_update/test_desktop_update_linux_gate.py index f7aa76f9b6..d88b1fab1f 100644 --- a/tests/test_desktop_update_linux_gate.py +++ b/tests/scripts/desktop_update/test_desktop_update_linux_gate.py @@ -16,7 +16,7 @@ from pathlib import Path import pytest -POSIX_SH = Path(__file__).resolve().parent.parent / "scripts" / "desktop-update" / "posix.sh" +POSIX_SH = Path(__file__).resolve().parent.parent.parent.parent / "scripts" / "desktop-update" / "posix.sh" pytestmark = pytest.mark.linux_only diff --git a/tests/test_desktop_update_shim_profile_cleanup.py b/tests/scripts/desktop_update/test_desktop_update_shim_profile_cleanup.py similarity index 97% rename from tests/test_desktop_update_shim_profile_cleanup.py rename to tests/scripts/desktop_update/test_desktop_update_shim_profile_cleanup.py index 980387d721..bc4d4a0449 100644 --- a/tests/test_desktop_update_shim_profile_cleanup.py +++ b/tests/scripts/desktop_update/test_desktop_update_shim_profile_cleanup.py @@ -39,7 +39,7 @@ def test_shim_removes_only_its_owned_profile(tmp_path, outcome): "HERMES_UPDATE_SHIM_GRACE_SECONDS": "1", "HERMES_SELFTEST_FAIL": "1" if outcome == "error" else ""} process = subprocess.Popen( ["bash", "-c", 'while [ ! -f "$HOME/start" ]; do sleep .05; done; exec bash "$@"', "probe", - str(Path(__file__).resolve().parents[1] / "scripts/desktop-update/posix.sh"), + str(Path(__file__).resolve().parents[3] / "scripts/desktop-update/posix.sh"), "--install-root", str(install), "--self-test-ui"], env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, ) diff --git a/tests/test_desktop_update_shim_progress.py b/tests/scripts/desktop_update/test_desktop_update_shim_progress.py similarity index 99% rename from tests/test_desktop_update_shim_progress.py rename to tests/scripts/desktop_update/test_desktop_update_shim_progress.py index 5af2ab9e96..53033683eb 100644 --- a/tests/test_desktop_update_shim_progress.py +++ b/tests/scripts/desktop_update/test_desktop_update_shim_progress.py @@ -18,7 +18,7 @@ from urllib.request import urlopen import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent SHIM_DIR = REPO_ROOT / "scripts" / "desktop-update" # posix.sh re-execs itself through these to detach from Electron's process diff --git a/tests/test_desktop_update_tcc_heal.py b/tests/scripts/desktop_update/test_desktop_update_tcc_heal.py similarity index 99% rename from tests/test_desktop_update_tcc_heal.py rename to tests/scripts/desktop_update/test_desktop_update_tcc_heal.py index a8eae77c49..a9610ed4d1 100644 --- a/tests/test_desktop_update_tcc_heal.py +++ b/tests/scripts/desktop_update/test_desktop_update_tcc_heal.py @@ -28,7 +28,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent POSIX_SH = REPO_ROOT / "scripts" / "desktop-update" / "posix.sh" GOOD_STUB = "#!/bin/bash\n# bootable interpreter stub\nexit 0\n" diff --git a/tests/test_desktop_update_windows_cwd.py b/tests/scripts/desktop_update/test_desktop_update_windows_cwd.py similarity index 96% rename from tests/test_desktop_update_windows_cwd.py rename to tests/scripts/desktop_update/test_desktop_update_windows_cwd.py index 7847da0497..c2ddbe8542 100644 --- a/tests/test_desktop_update_windows_cwd.py +++ b/tests/scripts/desktop_update/test_desktop_update_windows_cwd.py @@ -12,7 +12,7 @@ import pytest pytestmark = pytest.mark.windows_only -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent WINDOWS_UPDATE_PS1 = REPO_ROOT / "scripts" / "desktop-update" / "windows.ps1" diff --git a/tests/test_desktop_update_windows_pipe_drain.py b/tests/scripts/desktop_update/test_desktop_update_windows_pipe_drain.py similarity index 99% rename from tests/test_desktop_update_windows_pipe_drain.py rename to tests/scripts/desktop_update/test_desktop_update_windows_pipe_drain.py index d3cd1e8ee8..850f40bb46 100644 --- a/tests/test_desktop_update_windows_pipe_drain.py +++ b/tests/scripts/desktop_update/test_desktop_update_windows_pipe_drain.py @@ -52,7 +52,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent WINDOWS_PS1 = REPO_ROOT / "scripts" / "desktop-update" / "windows.ps1" diff --git a/tests/test_desktop_update_windows_progress.py b/tests/scripts/desktop_update/test_desktop_update_windows_progress.py similarity index 98% rename from tests/test_desktop_update_windows_progress.py rename to tests/scripts/desktop_update/test_desktop_update_windows_progress.py index 70708d754b..f9ee92abb5 100644 --- a/tests/test_desktop_update_windows_progress.py +++ b/tests/scripts/desktop_update/test_desktop_update_windows_progress.py @@ -22,7 +22,7 @@ import pytest pytestmark = pytest.mark.windows_only -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent WINDOWS_UPDATE_PS1 = REPO_ROOT / "scripts" / "desktop-update" / "windows.ps1" diff --git a/tests/test_desktop_update_windows_python_handoff.py b/tests/scripts/desktop_update/test_desktop_update_windows_python_handoff.py similarity index 98% rename from tests/test_desktop_update_windows_python_handoff.py rename to tests/scripts/desktop_update/test_desktop_update_windows_python_handoff.py index e63eb9dd28..679483a0c1 100644 --- a/tests/test_desktop_update_windows_python_handoff.py +++ b/tests/scripts/desktop_update/test_desktop_update_windows_python_handoff.py @@ -32,7 +32,7 @@ import re from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent WINDOWS_PS1 = REPO_ROOT / "scripts" / "desktop-update" / "windows.ps1" diff --git a/tests/test_desktop_update_windows_retry_policy.py b/tests/scripts/desktop_update/test_desktop_update_windows_retry_policy.py similarity index 96% rename from tests/test_desktop_update_windows_retry_policy.py rename to tests/scripts/desktop_update/test_desktop_update_windows_retry_policy.py index 8fafbca24c..c97105bea9 100644 --- a/tests/test_desktop_update_windows_retry_policy.py +++ b/tests/scripts/desktop_update/test_desktop_update_windows_retry_policy.py @@ -9,7 +9,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent RETRY_POLICY = REPO_ROOT / "scripts" / "desktop-update" / "retry-policy.ps1" diff --git a/tests/test_desktop_update_windows_timestamp.py b/tests/scripts/desktop_update/test_desktop_update_windows_timestamp.py similarity index 95% rename from tests/test_desktop_update_windows_timestamp.py rename to tests/scripts/desktop_update/test_desktop_update_windows_timestamp.py index 5efd0cc04b..e8bd8306d9 100644 --- a/tests/test_desktop_update_windows_timestamp.py +++ b/tests/scripts/desktop_update/test_desktop_update_windows_timestamp.py @@ -15,7 +15,7 @@ from __future__ import annotations from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent WINDOWS_UPDATE_PS1 = REPO_ROOT / "scripts" / "desktop-update" / "windows.ps1" diff --git a/tests/test_desktop_update_windows_ui_delivery.py b/tests/scripts/desktop_update/test_desktop_update_windows_ui_delivery.py similarity index 98% rename from tests/test_desktop_update_windows_ui_delivery.py rename to tests/scripts/desktop_update/test_desktop_update_windows_ui_delivery.py index dcd7b17c96..3558ee385d 100644 --- a/tests/test_desktop_update_windows_ui_delivery.py +++ b/tests/scripts/desktop_update/test_desktop_update_windows_ui_delivery.py @@ -16,7 +16,7 @@ from urllib.request import Request, urlopen import pytest pytestmark = pytest.mark.windows_only -SCRIPT = Path(__file__).resolve().parents[1] / "scripts/desktop-update/windows.ps1" +SCRIPT = Path(__file__).resolve().parents[3] / "scripts/desktop-update/windows.ps1" @contextmanager diff --git a/tests/test_install_autostash_conflict_recovery.py b/tests/scripts/install/test_install_autostash_conflict_recovery.py similarity index 99% rename from tests/test_install_autostash_conflict_recovery.py rename to tests/scripts/install/test_install_autostash_conflict_recovery.py index 94e19aeec5..210a082df4 100644 --- a/tests/test_install_autostash_conflict_recovery.py +++ b/tests/scripts/install/test_install_autostash_conflict_recovery.py @@ -15,7 +15,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1" POWERSHELL = next( diff --git a/tests/test_install_clone_throttle_fallback.py b/tests/scripts/install/test_install_clone_throttle_fallback.py similarity index 98% rename from tests/test_install_clone_throttle_fallback.py rename to tests/scripts/install/test_install_clone_throttle_fallback.py index cb47688932..973649e8ff 100644 --- a/tests/test_install_clone_throttle_fallback.py +++ b/tests/scripts/install/test_install_clone_throttle_fallback.py @@ -31,7 +31,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" pytestmark = pytest.mark.skipif( diff --git a/tests/test_install_commit_pin_rollback.py b/tests/scripts/install/test_install_commit_pin_rollback.py similarity index 98% rename from tests/test_install_commit_pin_rollback.py rename to tests/scripts/install/test_install_commit_pin_rollback.py index 4d6de7986c..390867c6fe 100644 --- a/tests/test_install_commit_pin_rollback.py +++ b/tests/scripts/install/test_install_commit_pin_rollback.py @@ -28,7 +28,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" pytestmark = pytest.mark.skipif( diff --git a/tests/test_install_diverged_update.py b/tests/scripts/install/test_install_diverged_update.py similarity index 97% rename from tests/test_install_diverged_update.py rename to tests/scripts/install/test_install_diverged_update.py index a042d8c483..04cb2db0a7 100644 --- a/tests/test_install_diverged_update.py +++ b/tests/scripts/install/test_install_diverged_update.py @@ -14,7 +14,7 @@ from __future__ import annotations import re from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1" diff --git a/tests/test_install_lockfile_churn.py b/tests/scripts/install/test_install_lockfile_churn.py similarity index 98% rename from tests/test_install_lockfile_churn.py rename to tests/scripts/install/test_install_lockfile_churn.py index 15861e4d01..0c02f636e6 100644 --- a/tests/test_install_lockfile_churn.py +++ b/tests/scripts/install/test_install_lockfile_churn.py @@ -19,7 +19,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1" diff --git a/tests/test_install_macos_launcher.py b/tests/scripts/install/test_install_macos_launcher.py similarity index 97% rename from tests/test_install_macos_launcher.py rename to tests/scripts/install/test_install_macos_launcher.py index fd46c347a7..7729ed3e03 100644 --- a/tests/test_install_macos_launcher.py +++ b/tests/scripts/install/test_install_macos_launcher.py @@ -10,7 +10,7 @@ import subprocess from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" diff --git a/tests/test_install_no_initial_commit.py b/tests/scripts/install/test_install_no_initial_commit.py similarity index 98% rename from tests/test_install_no_initial_commit.py rename to tests/scripts/install/test_install_no_initial_commit.py index 321ddd0b40..7f028f5f82 100644 --- a/tests/test_install_no_initial_commit.py +++ b/tests/scripts/install/test_install_no_initial_commit.py @@ -21,7 +21,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1" diff --git a/tests/test_install_ps1_ascii_only.py b/tests/scripts/install/test_install_ps1_ascii_only.py similarity index 97% rename from tests/test_install_ps1_ascii_only.py rename to tests/scripts/install/test_install_ps1_ascii_only.py index 71638620c2..30bfa9ed48 100644 --- a/tests/test_install_ps1_ascii_only.py +++ b/tests/scripts/install/test_install_ps1_ascii_only.py @@ -30,7 +30,7 @@ from __future__ import annotations from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1" diff --git a/tests/test_install_ps1_browser_install.py b/tests/scripts/install/test_install_ps1_browser_install.py similarity index 93% rename from tests/test_install_ps1_browser_install.py rename to tests/scripts/install/test_install_ps1_browser_install.py index 0a6b9b556a..372f648a52 100644 --- a/tests/test_install_ps1_browser_install.py +++ b/tests/scripts/install/test_install_ps1_browser_install.py @@ -8,14 +8,14 @@ only via `hermes update`'s npx cache warm or an actual browser-tool call's lazy npx resolution. Linux CI cannot execute the PowerShell installer, so verification here is -source-text-level only, matching tests/test_install_sh_browser_install.py -and tests/test_install_ps1_ascii_only.py. +source-text-level only, matching tests/scripts/install/test_install_sh_browser_install.py +and tests/scripts/install/test_install_ps1_ascii_only.py. """ import re from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1" diff --git a/tests/test_install_ps1_managed_node_swap.py b/tests/scripts/install/test_install_ps1_managed_node_swap.py similarity index 98% rename from tests/test_install_ps1_managed_node_swap.py rename to tests/scripts/install/test_install_ps1_managed_node_swap.py index 7ab389a95c..d17d06f2b9 100644 --- a/tests/test_install_ps1_managed_node_swap.py +++ b/tests/scripts/install/test_install_ps1_managed_node_swap.py @@ -26,7 +26,7 @@ from __future__ import annotations import re from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1" diff --git a/tests/test_install_ps1_managed_python_provenance.py b/tests/scripts/install/test_install_ps1_managed_python_provenance.py similarity index 99% rename from tests/test_install_ps1_managed_python_provenance.py rename to tests/scripts/install/test_install_ps1_managed_python_provenance.py index 7cccce360e..2678bec980 100644 --- a/tests/test_install_ps1_managed_python_provenance.py +++ b/tests/scripts/install/test_install_ps1_managed_python_provenance.py @@ -15,7 +15,7 @@ from tests.install_ps1_fake_uv import compile_fake_uv pytestmark = pytest.mark.windows_only -_INSTALL_PS1 = Path(__file__).resolve().parents[1] / "scripts" / "install.ps1" +_INSTALL_PS1 = Path(__file__).resolve().parents[3] / "scripts" / "install.ps1" def test_fresh_install_manifest_orders_repo_before_checkout_scoped_python( diff --git a/tests/test_install_ps1_native_stderr_eap.py b/tests/scripts/install/test_install_ps1_native_stderr_eap.py similarity index 97% rename from tests/test_install_ps1_native_stderr_eap.py rename to tests/scripts/install/test_install_ps1_native_stderr_eap.py index 6afcd0cb87..7ec4df9fbe 100644 --- a/tests/test_install_ps1_native_stderr_eap.py +++ b/tests/scripts/install/test_install_ps1_native_stderr_eap.py @@ -11,7 +11,7 @@ from __future__ import annotations import re from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1" diff --git a/tests/test_install_ps1_node_path_for_npm.py b/tests/scripts/install/test_install_ps1_node_path_for_npm.py similarity index 95% rename from tests/test_install_ps1_node_path_for_npm.py rename to tests/scripts/install/test_install_ps1_node_path_for_npm.py index 9f3a6cb432..47f296f39c 100644 --- a/tests/test_install_ps1_node_path_for_npm.py +++ b/tests/scripts/install/test_install_ps1_node_path_for_npm.py @@ -10,7 +10,7 @@ from __future__ import annotations import re from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1" diff --git a/tests/test_install_ps1_python_fallback_venv.py b/tests/scripts/install/test_install_ps1_python_fallback_venv.py similarity index 98% rename from tests/test_install_ps1_python_fallback_venv.py rename to tests/scripts/install/test_install_ps1_python_fallback_venv.py index 91c2286942..58ef44f1a8 100644 --- a/tests/test_install_ps1_python_fallback_venv.py +++ b/tests/scripts/install/test_install_ps1_python_fallback_venv.py @@ -28,7 +28,7 @@ from pathlib import Path import pytest -_INSTALL_PS1 = Path(__file__).resolve().parents[1] / "scripts" / "install.ps1" +_INSTALL_PS1 = Path(__file__).resolve().parents[3] / "scripts" / "install.ps1" @pytest.fixture(scope="module") diff --git a/tests/test_install_ps1_resolver_strictmode.py b/tests/scripts/install/test_install_ps1_resolver_strictmode.py similarity index 98% rename from tests/test_install_ps1_resolver_strictmode.py rename to tests/scripts/install/test_install_ps1_resolver_strictmode.py index dbdf921c7f..c5d9d60b7a 100644 --- a/tests/test_install_ps1_resolver_strictmode.py +++ b/tests/scripts/install/test_install_ps1_resolver_strictmode.py @@ -24,7 +24,7 @@ from __future__ import annotations import re from pathlib import Path -_INSTALL_PS1 = Path(__file__).resolve().parents[1] / "scripts" / "install.ps1" +_INSTALL_PS1 = Path(__file__).resolve().parents[3] / "scripts" / "install.ps1" def _ps1() -> str: diff --git a/tests/test_install_ps1_uv_install_fallback.py b/tests/scripts/install/test_install_ps1_uv_install_fallback.py similarity index 98% rename from tests/test_install_ps1_uv_install_fallback.py rename to tests/scripts/install/test_install_ps1_uv_install_fallback.py index 0871fd94d1..9435c07a9d 100644 --- a/tests/test_install_ps1_uv_install_fallback.py +++ b/tests/scripts/install/test_install_ps1_uv_install_fallback.py @@ -32,7 +32,7 @@ from pathlib import Path import pytest -_INSTALL_PS1 = Path(__file__).resolve().parents[1] / "scripts" / "install.ps1" +_INSTALL_PS1 = Path(__file__).resolve().parents[3] / "scripts" / "install.ps1" _GITHUB_INSTALLER_URL = ( "https://github.com/astral-sh/uv/releases/latest/download/uv-installer.ps1" diff --git a/tests/test_install_ps1_uv_powershell_host.py b/tests/scripts/install/test_install_ps1_uv_powershell_host.py similarity index 98% rename from tests/test_install_ps1_uv_powershell_host.py rename to tests/scripts/install/test_install_ps1_uv_powershell_host.py index ea442ce484..89df184ead 100644 --- a/tests/test_install_ps1_uv_powershell_host.py +++ b/tests/scripts/install/test_install_ps1_uv_powershell_host.py @@ -24,7 +24,7 @@ from pathlib import Path import pytest -_INSTALL_PS1 = Path(__file__).resolve().parents[1] / "scripts" / "install.ps1" +_INSTALL_PS1 = Path(__file__).resolve().parents[3] / "scripts" / "install.ps1" @pytest.fixture(scope="module") diff --git a/tests/test_install_ps1_venv_process_tree.py b/tests/scripts/install/test_install_ps1_venv_process_tree.py similarity index 98% rename from tests/test_install_ps1_venv_process_tree.py rename to tests/scripts/install/test_install_ps1_venv_process_tree.py index 23e817e281..05df59c6ee 100644 --- a/tests/test_install_ps1_venv_process_tree.py +++ b/tests/scripts/install/test_install_ps1_venv_process_tree.py @@ -20,7 +20,7 @@ import pytest from tests.install_ps1_fake_uv import compile_fake_uv -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1" POWERSHELL = next( (candidate for candidate in ("powershell", "pwsh") if shutil.which(candidate)), diff --git a/tests/test_install_ps1_venv_recreate_safety.py b/tests/scripts/install/test_install_ps1_venv_recreate_safety.py similarity index 97% rename from tests/test_install_ps1_venv_recreate_safety.py rename to tests/scripts/install/test_install_ps1_venv_recreate_safety.py index 934a97fae0..542d3d582d 100644 --- a/tests/test_install_ps1_venv_recreate_safety.py +++ b/tests/scripts/install/test_install_ps1_venv_recreate_safety.py @@ -8,7 +8,7 @@ leaving a half-installed venv that the next health/blocker probe cannot use. from pathlib import Path -INSTALL_PS1 = Path(__file__).resolve().parents[1] / "scripts" / "install.ps1" +INSTALL_PS1 = Path(__file__).resolve().parents[3] / "scripts" / "install.ps1" def _function_body(source: str, function_name: str) -> str: diff --git a/tests/test_install_ps1_venv_rename_abort.py b/tests/scripts/install/test_install_ps1_venv_rename_abort.py similarity index 97% rename from tests/test_install_ps1_venv_rename_abort.py rename to tests/scripts/install/test_install_ps1_venv_rename_abort.py index a779841709..e59ca8ec93 100644 --- a/tests/test_install_ps1_venv_rename_abort.py +++ b/tests/scripts/install/test_install_ps1_venv_rename_abort.py @@ -13,7 +13,7 @@ from pathlib import Path import pytest -_INSTALL_PS1 = Path(__file__).resolve().parents[1] / "scripts" / "install.ps1" +_INSTALL_PS1 = Path(__file__).resolve().parents[3] / "scripts" / "install.ps1" @pytest.fixture(scope="module") diff --git a/tests/test_install_ps1_venv_transaction_boundary.py b/tests/scripts/install/test_install_ps1_venv_transaction_boundary.py similarity index 96% rename from tests/test_install_ps1_venv_transaction_boundary.py rename to tests/scripts/install/test_install_ps1_venv_transaction_boundary.py index 52f4183ec8..b0ddf23911 100644 --- a/tests/test_install_ps1_venv_transaction_boundary.py +++ b/tests/scripts/install/test_install_ps1_venv_transaction_boundary.py @@ -17,12 +17,12 @@ The contract locked here: baseline-import gate passes (``Complete-VenvTransaction``). The script only runs on Windows, so Linux CI locks the contract at the -source level, same approach as tests/test_install_ps1_venv_recreate_safety.py. +source level, same approach as tests/scripts/install/test_install_ps1_venv_recreate_safety.py. """ from pathlib import Path -INSTALL_PS1 = Path(__file__).resolve().parents[1] / "scripts" / "install.ps1" +INSTALL_PS1 = Path(__file__).resolve().parents[3] / "scripts" / "install.ps1" def _function_body(source: str, function_name: str) -> str: diff --git a/tests/test_install_ps1_web_server_syntax_probe.py b/tests/scripts/install/test_install_ps1_web_server_syntax_probe.py similarity index 96% rename from tests/test_install_ps1_web_server_syntax_probe.py rename to tests/scripts/install/test_install_ps1_web_server_syntax_probe.py index 14f99f725c..6378d509a0 100644 --- a/tests/test_install_ps1_web_server_syntax_probe.py +++ b/tests/scripts/install/test_install_ps1_web_server_syntax_probe.py @@ -17,7 +17,7 @@ import re from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1" diff --git a/tests/test_install_scripts_computer_use.py b/tests/scripts/install/test_install_scripts_computer_use.py similarity index 97% rename from tests/test_install_scripts_computer_use.py rename to tests/scripts/install/test_install_scripts_computer_use.py index e213a2653d..cb6d8975f1 100644 --- a/tests/test_install_scripts_computer_use.py +++ b/tests/scripts/install/test_install_scripts_computer_use.py @@ -8,7 +8,7 @@ binary is still missing (see test_web_routers_tools_install_on_enable.py). from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1" diff --git a/tests/test_install_sh_acp_launcher.py b/tests/scripts/install/test_install_sh_acp_launcher.py similarity index 98% rename from tests/test_install_sh_acp_launcher.py rename to tests/scripts/install/test_install_sh_acp_launcher.py index 27cb4c8f1a..0a5e30a2e1 100644 --- a/tests/test_install_sh_acp_launcher.py +++ b/tests/scripts/install/test_install_sh_acp_launcher.py @@ -16,7 +16,7 @@ import subprocess from pathlib import Path from unittest.mock import patch -INSTALL_SH = Path(__file__).resolve().parent.parent / "scripts" / "install.sh" +INSTALL_SH = Path(__file__).resolve().parent.parent.parent.parent / "scripts" / "install.sh" ACP_BLOCK = re.compile( r'( rm -f "\$command_link_dir/hermes-acp".*?' diff --git a/tests/test_install_sh_bootstrap_marker.py b/tests/scripts/install/test_install_sh_bootstrap_marker.py similarity index 98% rename from tests/test_install_sh_bootstrap_marker.py rename to tests/scripts/install/test_install_sh_bootstrap_marker.py index 6eab5d6599..ec403006f3 100644 --- a/tests/test_install_sh_bootstrap_marker.py +++ b/tests/scripts/install/test_install_sh_bootstrap_marker.py @@ -16,7 +16,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" diff --git a/tests/test_install_sh_browser_install.py b/tests/scripts/install/test_install_sh_browser_install.py similarity index 99% rename from tests/test_install_sh_browser_install.py rename to tests/scripts/install/test_install_sh_browser_install.py index a080bcdde2..e2f5f0323c 100644 --- a/tests/test_install_sh_browser_install.py +++ b/tests/scripts/install/test_install_sh_browser_install.py @@ -8,7 +8,7 @@ unsupported distribution. from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" diff --git a/tests/test_install_sh_install_method_stamp.py b/tests/scripts/install/test_install_sh_install_method_stamp.py similarity index 96% rename from tests/test_install_sh_install_method_stamp.py rename to tests/scripts/install/test_install_sh_install_method_stamp.py index b2a1b7da8c..6ca5f09ed2 100644 --- a/tests/test_install_sh_install_method_stamp.py +++ b/tests/scripts/install/test_install_sh_install_method_stamp.py @@ -20,7 +20,7 @@ from __future__ import annotations import re from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" diff --git a/tests/test_install_sh_node_deps_failure.py b/tests/scripts/install/test_install_sh_node_deps_failure.py similarity index 98% rename from tests/test_install_sh_node_deps_failure.py rename to tests/scripts/install/test_install_sh_node_deps_failure.py index c42e0cb32c..ae60258b1b 100644 --- a/tests/test_install_sh_node_deps_failure.py +++ b/tests/scripts/install/test_install_sh_node_deps_failure.py @@ -8,7 +8,7 @@ import subprocess from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" diff --git a/tests/test_install_sh_node_deps_workspaces.py b/tests/scripts/install/test_install_sh_node_deps_workspaces.py similarity index 98% rename from tests/test_install_sh_node_deps_workspaces.py rename to tests/scripts/install/test_install_sh_node_deps_workspaces.py index 176e9efad3..a5e9236dd3 100644 --- a/tests/test_install_sh_node_deps_workspaces.py +++ b/tests/scripts/install/test_install_sh_node_deps_workspaces.py @@ -18,7 +18,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" pytestmark = pytest.mark.skipif(shutil.which("bash") is None, reason="needs bash") diff --git a/tests/test_install_sh_node_global_prefix.py b/tests/scripts/install/test_install_sh_node_global_prefix.py similarity index 97% rename from tests/test_install_sh_node_global_prefix.py rename to tests/scripts/install/test_install_sh_node_global_prefix.py index d4293a0d59..f2ac4cb929 100644 --- a/tests/test_install_sh_node_global_prefix.py +++ b/tests/scripts/install/test_install_sh_node_global_prefix.py @@ -14,7 +14,7 @@ node/npm/npx), scoped to the bundled Node via its prefix-local global npmrc. from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" NODE_BOOTSTRAP = REPO_ROOT / "scripts" / "lib" / "node-bootstrap.sh" diff --git a/tests/test_install_sh_node_npm_check.py b/tests/scripts/install/test_install_sh_node_npm_check.py similarity index 96% rename from tests/test_install_sh_node_npm_check.py rename to tests/scripts/install/test_install_sh_node_npm_check.py index 6fa33e9bae..2c1f84319c 100644 --- a/tests/test_install_sh_node_npm_check.py +++ b/tests/scripts/install/test_install_sh_node_npm_check.py @@ -9,7 +9,7 @@ when the install actually failed. from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" diff --git a/tests/test_install_sh_node_prerelease.py b/tests/scripts/install/test_install_sh_node_prerelease.py similarity index 98% rename from tests/test_install_sh_node_prerelease.py rename to tests/scripts/install/test_install_sh_node_prerelease.py index 2b2a613ed9..3f07dbb87e 100644 --- a/tests/test_install_sh_node_prerelease.py +++ b/tests/scripts/install/test_install_sh_node_prerelease.py @@ -19,7 +19,7 @@ import re import subprocess from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" NODE_BOOTSTRAP = REPO_ROOT / "scripts" / "lib" / "node-bootstrap.sh" diff --git a/tests/test_install_sh_node_probe_87460.py b/tests/scripts/install/test_install_sh_node_probe.py similarity index 99% rename from tests/test_install_sh_node_probe_87460.py rename to tests/scripts/install/test_install_sh_node_probe.py index 83e50e10a4..6bdd9282ba 100644 --- a/tests/test_install_sh_node_probe_87460.py +++ b/tests/scripts/install/test_install_sh_node_probe.py @@ -19,7 +19,7 @@ import tarfile from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" diff --git a/tests/test_install_sh_node_tarball_without_xz.py b/tests/scripts/install/test_install_sh_node_tarball_without_xz.py similarity index 97% rename from tests/test_install_sh_node_tarball_without_xz.py rename to tests/scripts/install/test_install_sh_node_tarball_without_xz.py index 468306acc9..24c02cce79 100644 --- a/tests/test_install_sh_node_tarball_without_xz.py +++ b/tests/scripts/install/test_install_sh_node_tarball_without_xz.py @@ -16,7 +16,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INDEX = "node-v26.7.0-linux-x64.tar.xz\nnode-v26.7.0-linux-x64.tar.gz\n" diff --git a/tests/test_install_sh_pythonpath_sanitization.py b/tests/scripts/install/test_install_sh_pythonpath_sanitization.py similarity index 94% rename from tests/test_install_sh_pythonpath_sanitization.py rename to tests/scripts/install/test_install_sh_pythonpath_sanitization.py index 0fd4c14d92..533a1da3e1 100644 --- a/tests/test_install_sh_pythonpath_sanitization.py +++ b/tests/scripts/install/test_install_sh_pythonpath_sanitization.py @@ -8,7 +8,7 @@ must sanitize those vars both during installation and at runtime launch. from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" diff --git a/tests/test_install_sh_reuse_supported_python.py b/tests/scripts/install/test_install_sh_reuse_supported_python.py similarity index 97% rename from tests/test_install_sh_reuse_supported_python.py rename to tests/scripts/install/test_install_sh_reuse_supported_python.py index 82805ec971..d5f477d1fa 100644 --- a/tests/test_install_sh_reuse_supported_python.py +++ b/tests/scripts/install/test_install_sh_reuse_supported_python.py @@ -10,7 +10,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" diff --git a/tests/test_install_sh_root_fhs_uv_python_path.py b/tests/scripts/install/test_install_sh_root_fhs_uv_python_path.py similarity index 97% rename from tests/test_install_sh_root_fhs_uv_python_path.py rename to tests/scripts/install/test_install_sh_root_fhs_uv_python_path.py index 0f1c5fa725..31cb2695d0 100644 --- a/tests/test_install_sh_root_fhs_uv_python_path.py +++ b/tests/scripts/install/test_install_sh_root_fhs_uv_python_path.py @@ -10,7 +10,7 @@ and the shared ``/usr/local/bin/hermes`` wrapper fails for non-root users with from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" diff --git a/tests/test_install_sh_setup_wizard_tty_probe.py b/tests/scripts/install/test_install_sh_setup_wizard_tty_probe.py similarity index 98% rename from tests/test_install_sh_setup_wizard_tty_probe.py rename to tests/scripts/install/test_install_sh_setup_wizard_tty_probe.py index a9f8a26e75..b36cd322ef 100644 --- a/tests/test_install_sh_setup_wizard_tty_probe.py +++ b/tests/scripts/install/test_install_sh_setup_wizard_tty_probe.py @@ -20,7 +20,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" # Every function in scripts/install.sh that previously gated on a bare diff --git a/tests/test_install_sh_symlink_stomp.py b/tests/scripts/install/test_install_sh_symlink_stomp.py similarity index 98% rename from tests/test_install_sh_symlink_stomp.py rename to tests/scripts/install/test_install_sh_symlink_stomp.py index 0fbe508509..21deab8f85 100644 --- a/tests/test_install_sh_symlink_stomp.py +++ b/tests/scripts/install/test_install_sh_symlink_stomp.py @@ -22,7 +22,7 @@ from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" diff --git a/tests/test_install_sh_termux_network_prereqs.py b/tests/scripts/install/test_install_sh_termux_network_prereqs.py similarity index 92% rename from tests/test_install_sh_termux_network_prereqs.py rename to tests/scripts/install/test_install_sh_termux_network_prereqs.py index 891cf54d13..ebd1dac460 100644 --- a/tests/test_install_sh_termux_network_prereqs.py +++ b/tests/scripts/install/test_install_sh_termux_network_prereqs.py @@ -3,7 +3,7 @@ from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" diff --git a/tests/test_install_sh_termux_python_bounds.py b/tests/scripts/install/test_install_sh_termux_python_bounds.py similarity index 99% rename from tests/test_install_sh_termux_python_bounds.py rename to tests/scripts/install/test_install_sh_termux_python_bounds.py index 443873bf64..f4fac1d3e6 100644 --- a/tests/test_install_sh_termux_python_bounds.py +++ b/tests/scripts/install/test_install_sh_termux_python_bounds.py @@ -10,7 +10,7 @@ import sys from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" SETUP_HERMES_SH = REPO_ROOT / "setup-hermes.sh" diff --git a/tests/test_install_sh_uv_lock_config.py b/tests/scripts/install/test_install_sh_uv_lock_config.py similarity index 98% rename from tests/test_install_sh_uv_lock_config.py rename to tests/scripts/install/test_install_sh_uv_lock_config.py index 59d5bba96d..f9f6dca113 100644 --- a/tests/test_install_sh_uv_lock_config.py +++ b/tests/scripts/install/test_install_sh_uv_lock_config.py @@ -10,7 +10,7 @@ import subprocess import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SCRIPTS = ( REPO_ROOT / "scripts" / "install.sh", REPO_ROOT / "setup-hermes.sh", diff --git a/tests/test_install_unmerged_index.py b/tests/scripts/install/test_install_unmerged_index.py similarity index 99% rename from tests/test_install_unmerged_index.py rename to tests/scripts/install/test_install_unmerged_index.py index ade18486c9..cc15998de7 100644 --- a/tests/test_install_unmerged_index.py +++ b/tests/scripts/install/test_install_unmerged_index.py @@ -20,7 +20,7 @@ from pathlib import Path import pytest -REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent INSTALL_SH = REPO_ROOT / "scripts" / "install.sh" INSTALL_PS1 = REPO_ROOT / "scripts" / "install.ps1" diff --git a/tests/test_run_tests_parallel.py b/tests/scripts/test_run_tests_parallel.py similarity index 97% rename from tests/test_run_tests_parallel.py rename to tests/scripts/test_run_tests_parallel.py index f83e9a5563..aeee6cfa0e 100644 --- a/tests/test_run_tests_parallel.py +++ b/tests/scripts/test_run_tests_parallel.py @@ -65,7 +65,7 @@ def _pid_alive(pid: int) -> bool: def test_progress_output_tolerates_legacy_stdout_encoding(tmp_path: Path) -> None: """Progress glyphs must not crash the runner on non-UTF-8 consoles.""" - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent runner = repo_root / "scripts" / "run_tests_parallel.py" probe_dir = tmp_path / "probe" @@ -111,7 +111,7 @@ def test_grandchild_leak_is_killed_by_runner(tmp_path: Path) -> None: 3. Wait for the grandchild PID to vanish (poll for ~5s). 4. Assert the runner exited cleanly AND the grandchild is dead. """ - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent runner = repo_root / "scripts" / "run_tests_parallel.py" assert runner.exists(), f"runner missing at {runner}" @@ -253,7 +253,7 @@ def _make_probe_dir(tmp_path: Path) -> Path: def _run_runner(probe_dir: Path, *extra: str) -> subprocess.CompletedProcess: - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent runner = repo_root / "scripts" / "run_tests_parallel.py" return subprocess.run( [sys.executable, str(runner), "--paths", str(probe_dir), @@ -297,7 +297,7 @@ def test_bare_value_flag_keeps_its_value(tmp_path: Path) -> None: def test_positional_path_not_treated_as_flag(tmp_path: Path) -> None: """A positional path arg still overrides discovery (not routed to pytest).""" probe_dir = _make_probe_dir(tmp_path) - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent runner = repo_root / "scripts" / "run_tests_parallel.py" # Pass the probe dir positionally (no --paths), plus a bare -q. proc = subprocess.run( @@ -314,7 +314,7 @@ def test_positional_path_not_treated_as_flag(tmp_path: Path) -> None: def test_file_retry_self_heals_and_prints_both_attempts(tmp_path: Path) -> None: """A pass-on-retry is green, loud, and retains the failing traceback.""" - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent runner = repo_root / "scripts" / "run_tests_parallel.py" marker = tmp_path / "ran-once" probe = tmp_path / "test_flaky_probe.py" @@ -386,7 +386,7 @@ def test_node_id_selector_runs_the_named_test(tmp_path: Path) -> None: """``file.py::test_alpha`` runs that test instead of discovering nothing.""" probe_dir = _make_probe_dir(tmp_path) target = probe_dir / "test_flagprobe.py" - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent proc = subprocess.run( [sys.executable, str(repo_root / "scripts" / "run_tests_parallel.py"), f"{target}::test_alpha", "-j", "1", "--file-timeout", "30"], @@ -404,7 +404,7 @@ def test_explicit_k_wins_over_node_id_inference(tmp_path: Path) -> None: """A caller's own ``-k`` is not overridden by the node-id translation.""" probe_dir = _make_probe_dir(tmp_path) target = probe_dir / "test_flagprobe.py" - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent proc = subprocess.run( [sys.executable, str(repo_root / "scripts" / "run_tests_parallel.py"), f"{target}::test_alpha", "-k", "test_beta", @@ -430,7 +430,7 @@ def test_multiple_absolute_paths_split_on_pathsep(tmp_path: Path) -> None: (dir_b / "test_flagprobe_b.py").write_text( "def test_gamma():\n assert True\n" ) - repo_root = Path(__file__).resolve().parent.parent + repo_root = Path(__file__).resolve().parent.parent.parent runner = repo_root / "scripts" / "run_tests_parallel.py" proc = subprocess.run( [sys.executable, str(runner), diff --git a/tests/test_run_tests_parallel_stdio.py b/tests/scripts/test_run_tests_parallel_stdio.py similarity index 97% rename from tests/test_run_tests_parallel_stdio.py rename to tests/scripts/test_run_tests_parallel_stdio.py index 2add57c3e2..f1dbad3084 100644 --- a/tests/test_run_tests_parallel_stdio.py +++ b/tests/scripts/test_run_tests_parallel_stdio.py @@ -14,7 +14,7 @@ import io import sys from pathlib import Path -REPO_ROOT = Path(__file__).resolve().parents[1] +REPO_ROOT = Path(__file__).resolve().parents[2] _RUNNER_PATH = REPO_ROOT / "scripts" / "run_tests_parallel.py" diff --git a/tests/test_evidence_store.py b/tests/skills/test_evidence_store.py similarity index 99% rename from tests/test_evidence_store.py rename to tests/skills/test_evidence_store.py index 33bac2dcdb..7d6cadc32f 100644 --- a/tests/test_evidence_store.py +++ b/tests/skills/test_evidence_store.py @@ -3,7 +3,7 @@ from pathlib import Path import importlib.util # Load the hyphenated script name dynamically -repo_root = Path(__file__).parent.parent +repo_root = Path(__file__).parent.parent.parent script_path = repo_root / "optional-skills" / "security" / "oss-forensics" / "scripts" / "evidence-store.py" spec = importlib.util.spec_from_file_location("evidence_store", str(script_path)) diff --git a/tests/test_minisweagent_path.py b/tests/test_minisweagent_path.py deleted file mode 100644 index 965e4cfd59..0000000000 --- a/tests/test_minisweagent_path.py +++ /dev/null @@ -1,2 +0,0 @@ -# This file intentionally left empty. -# minisweagent_path.py was removed — see PR #2804. diff --git a/tests/test_model_picker_scroll.py b/tests/test_model_picker_scroll.py deleted file mode 100644 index 4698aef956..0000000000 --- a/tests/test_model_picker_scroll.py +++ /dev/null @@ -1,100 +0,0 @@ -"""Tests for the scrolling viewport logic in _curses_prompt_choice (issue #5755). - -The "More providers" submenu has 13 entries (11 extended + custom + cancel). -Before the fix, _curses_prompt_choice rendered items starting unconditionally -from index 0 with no scroll offset. On terminals shorter than ~16 rows, items -near the bottom were never drawn. When the cursor wrapped from 0 to the last -item (Cancel) via UP-arrow, the highlight rendered off-screen, leaving the menu -looking like only "Cancel" existed. - -The fix adds a scroll_offset that tracks the cursor so the highlighted item -is always within the visible window. These tests exercise that logic in -isolation without requiring a real TTY. -""" - - - - -# --------------------------------------------------------------------------- -# Pure scroll-offset logic extracted from _curses_menu for unit testing -# --------------------------------------------------------------------------- - -def _compute_scroll_offset(cursor: int, scroll_offset: int, visible: int, n_choices: int) -> int: - """Mirror of the scroll adjustment block inside _curses_menu.""" - if cursor < scroll_offset: - scroll_offset = cursor - elif cursor >= scroll_offset + visible: - scroll_offset = cursor - visible + 1 - scroll_offset = max(0, min(scroll_offset, max(0, n_choices - visible))) - return scroll_offset - - -def _visible_indices(cursor: int, scroll_offset: int, visible: int, n_choices: int): - """Return the list indices that would be rendered for the given state.""" - scroll_offset = _compute_scroll_offset(cursor, scroll_offset, visible, n_choices) - return list(range(scroll_offset, min(scroll_offset + visible, n_choices))) - - -# --------------------------------------------------------------------------- -# Tests: scroll offset calculation -# --------------------------------------------------------------------------- - -class TestScrollOffsetLogic: - N = 13 # typical extended-providers list length - - def test_cursor_at_zero_no_scroll(self): - """Start position: offset stays 0, first items visible.""" - assert _compute_scroll_offset(0, 0, 8, self.N) == 0 - - def test_cursor_within_window_unchanged(self): - """Cursor inside the current window: offset unchanged.""" - assert _compute_scroll_offset(5, 0, 8, self.N) == 0 - - - def test_cursor_wraps_to_cancel_via_up(self): - """UP from index 0 wraps to last item; last item must be visible.""" - wrapped_cursor = (0 - 1) % self.N # == 12 - indices = _visible_indices(wrapped_cursor, 0, 8, self.N) - assert wrapped_cursor in indices - - - def test_visible_window_never_exceeds_list(self): - """Offset is clamped so the window never starts past the list end.""" - offset = _compute_scroll_offset(12, 0, 20, self.N) # window larger than list - assert offset == 0 - - - def test_list_fits_in_window_no_scroll_needed(self): - """If all choices fit in the visible window, offset is always 0.""" - for cursor in range(self.N): - offset = _compute_scroll_offset(cursor, 0, 20, self.N) - assert offset == 0, f"cursor={cursor} should not scroll when window > list" - - def test_cursor_always_in_visible_range(self): - """Invariant: cursor is always within the rendered window after adjustment.""" - visible = 5 - for cursor in range(self.N): - indices = _visible_indices(cursor, 0, visible, self.N) - assert cursor in indices, f"cursor={cursor} not in visible={indices}" - - def test_full_navigation_down_cursor_always_visible(self): - """Simulate pressing DOWN through all items; cursor always in view.""" - visible = 6 - scroll_offset = 0 - cursor = 0 - for _ in range(self.N + 2): # wrap around twice - scroll_offset = _compute_scroll_offset(cursor, scroll_offset, visible, self.N) - rendered = list(range(scroll_offset, min(scroll_offset + visible, self.N))) - assert cursor in rendered, f"cursor={cursor} not in rendered={rendered}" - cursor = (cursor + 1) % self.N - - def test_full_navigation_up_cursor_always_visible(self): - """Simulate pressing UP through all items; cursor always in view.""" - visible = 6 - scroll_offset = 0 - cursor = 0 - for _ in range(self.N + 2): - scroll_offset = _compute_scroll_offset(cursor, scroll_offset, visible, self.N) - rendered = list(range(scroll_offset, min(scroll_offset + visible, self.N))) - assert cursor in rendered, f"cursor={cursor} not in rendered={rendered}" - cursor = (cursor - 1) % self.N diff --git a/tests/test_process_loop_event_loop_warning.py b/tests/test_process_loop_event_loop_warning.py deleted file mode 100644 index 7251060ef8..0000000000 --- a/tests/test_process_loop_event_loop_warning.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Tests for the process_loop RuntimeWarning fix -- issue #19285. - -In Python 3.10+, calling asyncio.get_event_loop() from a non-main thread -that has no current event loop emits a DeprecationWarning (3.10/3.11) or -RuntimeWarning (3.12+). The fix replaces get_event_loop() with -get_running_loop(), which raises RuntimeError (no warning) when there is no -running loop. -""" - -import asyncio -import threading -import warnings - - -class TestGetRunningLoopReplacement: - - def test_get_running_loop_raises_runtime_error_not_warning(self): - warnings_caught = [] - - def _thread_target(): - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - try: - asyncio.get_running_loop() - except RuntimeError: - pass - warnings_caught.extend(w) - - t = threading.Thread(target=_thread_target, daemon=True) - t.start() - t.join(timeout=5) - - runtime_warnings = [ - x for x in warnings_caught - if issubclass(x.category, RuntimeWarning) - ] - assert runtime_warnings == [], ( - f"Unexpected RuntimeWarning(s): {[str(w.message) for w in runtime_warnings]}" - ) - - def test_get_running_loop_is_silent_get_event_loop_is_not(self): - caught_from_running = [] - - def _test_get_running_loop(): - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - try: - asyncio.get_running_loop() - except RuntimeError: - pass - caught_from_running.extend(w) - - t = threading.Thread(target=_test_get_running_loop, daemon=True) - t.start() - t.join(timeout=5) - - assert all( - not issubclass(w.category, RuntimeWarning) - for w in caught_from_running - ), "get_running_loop() must never emit RuntimeWarning" - - def test_get_running_loop_returns_loop_when_running(self): - async def _check(): - loop = asyncio.get_running_loop() - assert loop is not None - assert loop.is_running() - - asyncio.run(_check()) - - def test_no_warning_from_background_thread_with_fix(self): - warnings_caught = [] - - def _thread_target(): - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - try: - current_loop = asyncio.get_running_loop() - except RuntimeError: - current_loop = None - except Exception: - current_loop = None - assert current_loop is None - warnings_caught.extend(w) - - t = threading.Thread(target=_thread_target, daemon=True) - t.start() - t.join(timeout=5) - - runtime_warnings = [ - x for x in warnings_caught - if issubclass(x.category, RuntimeWarning) - ] - assert runtime_warnings == [], ( - f"RuntimeWarning emitted despite fix: " - f"{[str(w.message) for w in runtime_warnings]}" - ) - - def test_fixed_pattern_in_process_loop_context(self): - results = {} - warnings_list = [] - - def _process_loop_simulation(): - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - try: - current_loop = asyncio.get_running_loop() - except RuntimeError: - current_loop = None - except Exception: - current_loop = None - results["current_loop"] = current_loop - warnings_list.extend(w) - - t = threading.Thread( - target=_process_loop_simulation, - name="Thread-3 (process_loop)", - daemon=True, - ) - t.start() - t.join(timeout=5) - - assert results.get("current_loop") is None - runtime_warnings = [ - x for x in warnings_list - if issubclass(x.category, RuntimeWarning) - ] - assert runtime_warnings == [], ( - f"process_loop simulation still emits RuntimeWarning: " - f"{[str(w.message) for w in runtime_warnings]}" - ) diff --git a/tests/test_tests_tree_layout.py b/tests/test_tests_tree_layout.py new file mode 100644 index 0000000000..1cfd6d504e --- /dev/null +++ b/tests/test_tests_tree_layout.py @@ -0,0 +1,76 @@ +"""The tests/ tree mirrors the source tree. + +``scripts/run_tests.sh tests//`` is how a change gets its regression +coverage run, so a test filed under a directory that does not correspond to +the code it exercises is a test nobody runs when that code changes. Two +drifts had accumulated: parallel directories for one source package +(``tests/cli`` beside ``tests/hermes_cli``, ``tests/run_agent`` beside +``tests/agent``, ``tests/state`` beside ``tests/hermes_state``) and ~250 +loose files at ``tests/`` root that belonged to a package. + +Filenames also stopped carrying issue numbers: ``test_89315_x.py`` reads as +noise in a directory listing and the number belongs in the docstring, where +``git log -S`` and a reader can both find it with context. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +TESTS_ROOT = REPO_ROOT / "tests" + +# tests// directories that do not mirror a source directory but are +# legitimate homes: cross-cutting suites, fixtures, and script-family tests. +_NON_MIRROR_DIRS = { + "ci", "conformance", "dashboard", "desktop", "docker", "e2e", "evals", + "fakes", "fixtures", "honcho_plugin", "install", "integration", "manual", + "monitoring", "openviking_plugin", "perf_guards", "scripts", "secret_sources", + "security", "skills", "verify", "website", "computer_use", "hermes_state", +} + +# Root-level modules whose tests sit directly in tests/ (no package to mirror). +_ROOT_MODULE_STEMS = {p.stem for p in REPO_ROOT.glob("*.py")} + +_ISSUE_NUMBER = re.compile(r"(^test_\d{4,6}_)|(_\d{4,6}\.py$)") + + +def _source_dirs() -> set[str]: + return { + p.name + for p in REPO_ROOT.iterdir() + if p.is_dir() and (p / "__init__.py").exists() + } + + +def test_every_test_directory_mirrors_a_source_directory_or_is_declared(): + """No sibling directory for a source package that already has one.""" + source_dirs = _source_dirs() + offenders = sorted( + d.name + for d in TESTS_ROOT.iterdir() + if d.is_dir() + and d.name not in ("__pycache__",) + and d.name not in source_dirs + and d.name not in _NON_MIRROR_DIRS + ) + assert not offenders, ( + "tests/ directories that mirror no source package: " + f"{offenders}. Put the tests under tests// (tests/agent, " + "tests/hermes_cli, ...) or, for a genuinely cross-cutting suite, add the " + "name to _NON_MIRROR_DIRS in this file with a reason." + ) + + +def test_no_issue_numbers_in_test_filenames(): + """Issue numbers live in docstrings, where the reader gets context.""" + offenders = sorted( + str(p.relative_to(REPO_ROOT)) + for p in TESTS_ROOT.rglob("test_*.py") + if _ISSUE_NUMBER.search(p.name) + ) + assert not offenders, ( + f"Issue numbers in test filenames: {offenders}. Drop the number from the " + "name and cite the issue in the module docstring instead." + ) diff --git a/tests/test_yaml_indent_consistency_31999.py b/tests/test_yaml_indent_consistency.py similarity index 100% rename from tests/test_yaml_indent_consistency_31999.py rename to tests/test_yaml_indent_consistency.py diff --git a/tests/test_browser_vault.py b/tests/tools/test_browser_vault.py similarity index 99% rename from tests/test_browser_vault.py rename to tests/tools/test_browser_vault.py index 4487162e9d..30317f934d 100644 --- a/tests/test_browser_vault.py +++ b/tests/tools/test_browser_vault.py @@ -23,7 +23,7 @@ from unittest.mock import patch import pytest -sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) +sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent)) from agent.vault_login_classifier import ( # noqa: E402 ClassifiedLoginControl, diff --git a/tests/test_compaction_tool_refresh.py b/tests/tools/test_compaction_tool_refresh.py similarity index 98% rename from tests/test_compaction_tool_refresh.py rename to tests/tools/test_compaction_tool_refresh.py index 408303dfe7..5a3669dddb 100644 --- a/tests/test_compaction_tool_refresh.py +++ b/tests/tools/test_compaction_tool_refresh.py @@ -13,7 +13,7 @@ import sys import unittest from unittest.mock import patch -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) class _Agent: diff --git a/tests/tools/test_hub_lock_non_utf8_68053.py b/tests/tools/test_hub_lock_non_utf8.py similarity index 100% rename from tests/tools/test_hub_lock_non_utf8_68053.py rename to tests/tools/test_hub_lock_non_utf8.py diff --git a/tests/test_model_tools.py b/tests/tools/test_model_tools.py similarity index 100% rename from tests/test_model_tools.py rename to tests/tools/test_model_tools.py diff --git a/tests/test_plugin_compat_warning.py b/tests/tools/test_plugin_compat_warning.py similarity index 91% rename from tests/test_plugin_compat_warning.py rename to tests/tools/test_plugin_compat_warning.py index 45a7d799da..2dc9d9779d 100644 --- a/tests/test_plugin_compat_warning.py +++ b/tests/tools/test_plugin_compat_warning.py @@ -1,6 +1,6 @@ """The plugin-compat layer tells plugin authors where a name went, once, and internal code never trips it. -Kept alongside the compat layer (tests/test_compat_manifest_targets.py); both are deleted with it. +Kept alongside the compat layer (tests/hermes_cli/test_compat_manifest_targets.py); both are deleted with it. """ import importlib import json @@ -9,7 +9,7 @@ from pathlib import Path import pytest -ROOT = Path(__file__).resolve().parent.parent +ROOT = Path(__file__).resolve().parent.parent.parent MANIFEST = ROOT / "compat_manifest.json" pytestmark = pytest.mark.skipif(not MANIFEST.exists(), reason="compat layer removed (scheduled revert)") diff --git a/tests/test_plugin_skills.py b/tests/tools/test_plugin_skills.py similarity index 100% rename from tests/test_plugin_skills.py rename to tests/tools/test_plugin_skills.py diff --git a/tests/tools/test_pre_transcription_hook.py b/tests/tools/test_pre_transcription_hook.py index b9335db290..c650a32929 100644 --- a/tests/tools/test_pre_transcription_hook.py +++ b/tests/tools/test_pre_transcription_hook.py @@ -19,7 +19,7 @@ Covers: kwargs — no signature change. Mirrors the ``transform_tool_result`` hook test conventions from -``tests/test_transform_tool_result_hook.py``. +``tests/plugins/test_transform_tool_result_hook.py``. """ from __future__ import annotations diff --git a/tests/test_subprocess_home_isolation.py b/tests/tools/test_subprocess_home_isolation.py similarity index 100% rename from tests/test_subprocess_home_isolation.py rename to tests/tools/test_subprocess_home_isolation.py diff --git a/tests/tools/test_94248_timeout_transport_drain.py b/tests/tools/test_timeout_transport_drain.py similarity index 100% rename from tests/tools/test_94248_timeout_transport_drain.py rename to tests/tools/test_timeout_transport_drain.py diff --git a/tests/test_toolset_distributions.py b/tests/tools/test_toolset_distributions.py similarity index 100% rename from tests/test_toolset_distributions.py rename to tests/tools/test_toolset_distributions.py diff --git a/tests/test_toolsets.py b/tests/tools/test_toolsets.py similarity index 100% rename from tests/test_toolsets.py rename to tests/tools/test_toolsets.py diff --git a/tests/test_voice_max_recording_seconds.py b/tests/tools/test_voice_max_recording_seconds.py similarity index 100% rename from tests/test_voice_max_recording_seconds.py rename to tests/tools/test_voice_max_recording_seconds.py diff --git a/tests/tui_gateway/test_94895_backend_heartbeat.py b/tests/tui_gateway/test_backend_heartbeat.py similarity index 100% rename from tests/tui_gateway/test_94895_backend_heartbeat.py rename to tests/tui_gateway/test_backend_heartbeat.py diff --git a/tests/tui_gateway/test_entry_picker_prewarm.py b/tests/tui_gateway/test_entry_picker_prewarm.py index a4bb4d17a2..530cb9530b 100644 --- a/tests/tui_gateway/test_entry_picker_prewarm.py +++ b/tests/tui_gateway/test_entry_picker_prewarm.py @@ -17,7 +17,7 @@ guard is covered in ``tests/hermes_cli/test_picker_prewarm.py``): - A prewarm import/start failure is swallowed (fire-and-forget contract) and must not prevent ``main()`` from reaching the read loop. -Harness: same style as tests/test_tui_entry_mcp_owner.py — import +Harness: same style as tests/tui_gateway/test_tui_entry_mcp_owner.py — import ``tui_gateway.entry`` and monkeypatch its module attributes, running the real ``main()`` with stubbed I/O collaborators (no subprocess, no real gateway). """ diff --git a/tests/test_lazy_session_regressions.py b/tests/tui_gateway/test_lazy_session_regressions.py similarity index 100% rename from tests/test_lazy_session_regressions.py rename to tests/tui_gateway/test_lazy_session_regressions.py diff --git a/tests/tui_gateway/test_106459_routing_provenance_reopen.py b/tests/tui_gateway/test_routing_provenance_reopen.py similarity index 98% rename from tests/tui_gateway/test_106459_routing_provenance_reopen.py rename to tests/tui_gateway/test_routing_provenance_reopen.py index 2570c54ed2..7219d63f83 100644 --- a/tests/tui_gateway/test_106459_routing_provenance_reopen.py +++ b/tests/tui_gateway/test_routing_provenance_reopen.py @@ -76,7 +76,7 @@ def db(tmp_path: Path): @pytest.fixture def turn_env(monkeypatch, tmp_path, db): - """The immediate-prompt harness of tests/test_tui_gateway_server.py, with a real SessionDB.""" + """The immediate-prompt harness of tests/tui_gateway/test_tui_gateway_server.py, with a real SessionDB.""" for name, stub in { "_emit": lambda *_a, **_k: None, "make_stream_renderer": lambda _cols: None, "render_message": lambda _raw, _cols: None, "_wire_callbacks": lambda _sid: None, diff --git a/tests/test_slash_worker_watchdog.py b/tests/tui_gateway/test_slash_worker_watchdog.py similarity index 100% rename from tests/test_slash_worker_watchdog.py rename to tests/tui_gateway/test_slash_worker_watchdog.py diff --git a/tests/test_tui_entry_mcp_owner.py b/tests/tui_gateway/test_tui_entry_mcp_owner.py similarity index 100% rename from tests/test_tui_entry_mcp_owner.py rename to tests/tui_gateway/test_tui_entry_mcp_owner.py diff --git a/tests/test_tui_gateway_event_replay.py b/tests/tui_gateway/test_tui_gateway_event_replay.py similarity index 100% rename from tests/test_tui_gateway_event_replay.py rename to tests/tui_gateway/test_tui_gateway_event_replay.py diff --git a/tests/test_tui_gateway_loop_noise.py b/tests/tui_gateway/test_tui_gateway_loop_noise.py similarity index 100% rename from tests/test_tui_gateway_loop_noise.py rename to tests/tui_gateway/test_tui_gateway_loop_noise.py diff --git a/tests/test_tui_gateway_queue_on_busy.py b/tests/tui_gateway/test_tui_gateway_queue_on_busy.py similarity index 100% rename from tests/test_tui_gateway_queue_on_busy.py rename to tests/tui_gateway/test_tui_gateway_queue_on_busy.py diff --git a/tests/test_tui_gateway_server.py b/tests/tui_gateway/test_tui_gateway_server.py similarity index 100% rename from tests/test_tui_gateway_server.py rename to tests/tui_gateway/test_tui_gateway_server.py diff --git a/tests/test_tui_gateway_server_crash_history.py b/tests/tui_gateway/test_tui_gateway_server_crash_history.py similarity index 100% rename from tests/test_tui_gateway_server_crash_history.py rename to tests/tui_gateway/test_tui_gateway_server_crash_history.py diff --git a/tests/test_tui_gateway_ws.py b/tests/tui_gateway/test_tui_gateway_ws.py similarity index 100% rename from tests/test_tui_gateway_ws.py rename to tests/tui_gateway/test_tui_gateway_ws.py diff --git a/tests/test_tui_mcp_late_refresh.py b/tests/tui_gateway/test_tui_mcp_late_refresh.py similarity index 100% rename from tests/test_tui_mcp_late_refresh.py rename to tests/tui_gateway/test_tui_mcp_late_refresh.py diff --git a/tests/test_ws_keepalive_config.py b/tests/tui_gateway/test_ws_keepalive_config.py similarity index 100% rename from tests/test_ws_keepalive_config.py rename to tests/tui_gateway/test_ws_keepalive_config.py diff --git a/website/docs/developer-guide/acp-internals.md b/website/docs/developer-guide/acp-internals.md index e739d80871..a9cd83b748 100644 --- a/website/docs/developer-guide/acp-internals.md +++ b/website/docs/developer-guide/acp-internals.md @@ -175,7 +175,7 @@ ACP temporarily installs an approval callback on the terminal tool during prompt ## Related files -- `tests/acp/` — ACP test suite +- `tests/acp_adapter/` — ACP test suite - `toolsets.py` — `hermes-acp` toolset definition - `hermes_cli/main.py` — `hermes acp` CLI subcommand - `pyproject.toml` — `[acp]` optional dependency + `hermes-acp` script diff --git a/website/docs/developer-guide/adding-providers.md b/website/docs/developer-guide/adding-providers.md index 5d11383b69..ba8e276bd5 100644 --- a/website/docs/developer-guide/adding-providers.md +++ b/website/docs/developer-guide/adding-providers.md @@ -327,12 +327,12 @@ At minimum, touch the tests that guard provider wiring. Common places: - `tests/hermes_cli/test_runtime_provider_resolution.py` -- `tests/cli/test_cli_provider_resolution.py` +- `tests/hermes_cli/test_cli_provider_resolution.py` - `tests/hermes_cli/test_model_switch_custom_providers.py` (and adjacent `tests/hermes_cli/test_model_switch_*.py`) - `tests/hermes_cli/test_setup_model_provider.py` -- `tests/run_agent/test_provider_parity.py` -- `tests/run_agent/test_run_agent.py` -- `tests/test__adapter.py` for a native provider +- `tests/agent/test_provider_parity.py` +- `tests/agent/test_run_agent.py` +- `tests/agent/test__adapter.py` for a native provider For docs-only examples, the exact file set may differ. The point is to cover: @@ -347,7 +347,7 @@ Run the targeted tests (or use `scripts/run_tests.sh`, which runs each file in i ```bash source venv/bin/activate -python -m pytest tests/hermes_cli/test_runtime_provider_resolution.py tests/cli/test_cli_provider_resolution.py tests/hermes_cli/test_setup_model_provider.py tests/run_agent/test_provider_parity.py -q +python -m pytest tests/hermes_cli/test_runtime_provider_resolution.py tests/hermes_cli/test_cli_provider_resolution.py tests/hermes_cli/test_setup_model_provider.py tests/agent/test_provider_parity.py -q ``` For deeper changes, run the full suite before pushing: diff --git a/website/docs/developer-guide/egress-internals.md b/website/docs/developer-guide/egress-internals.md index 50852a8a36..4b0e5eaea6 100644 --- a/website/docs/developer-guide/egress-internals.md +++ b/website/docs/developer-guide/egress-internals.md @@ -46,18 +46,18 @@ tools/environments/docker.py _HERMES_EGRESS_NODE_OPTIONS_APPEND sentinel, enforce_on_docker precedence. -tests/test_iron_proxy.py Hermetic tests (~70). Binary install +tests/agent/test_iron_proxy.py Hermetic tests (~70). Binary install path, config build, mappings I/O, subprocess lifecycle, docker arg builder, deny CIDR defaults, bind policy, CA TOCTOU, ensure_audit_log behaviour, etc. -tests/test_iron_proxy_cli.py CLI handler unit tests (~20). Argparse +tests/hermes_cli/test_iron_proxy_cli.py CLI handler unit tests (~20). Argparse wiring, fail-loud paths, BWS refresh wire-up, dest='egress_command' regression guard. -tests/test_iron_proxy_e2e.py Live E2E (gated on HERMES_RUN_E2E=1). +tests/agent/test_iron_proxy_e2e.py Live E2E (gated on HERMES_RUN_E2E=1). Real iron-proxy binary, real curl, end-to-end token swap verified. ``` @@ -298,10 +298,10 @@ iron-proxy writes line-delimited JSON to `~/.hermes/proxy/iron-proxy.log` on the ```bash # Hermetic suite (no network, no real binary) -scripts/run_tests.sh tests/test_iron_proxy.py tests/test_iron_proxy_cli.py +scripts/run_tests.sh tests/agent/test_iron_proxy.py tests/hermes_cli/test_iron_proxy_cli.py # Live E2E (real binary, real curl, real CONNECT tunnel) -HERMES_RUN_E2E=1 scripts/run_tests.sh tests/test_iron_proxy_e2e.py +HERMES_RUN_E2E=1 scripts/run_tests.sh tests/agent/test_iron_proxy_e2e.py # Live PTY smoke against `hermes egress` HERMES_HOME=/tmp/hermes-egress-test python3 -m hermes_cli.main egress --help diff --git a/website/docs/developer-guide/memory-provider-plugin.md b/website/docs/developer-guide/memory-provider-plugin.md index 00f8fc948e..86b1f60de5 100644 --- a/website/docs/developer-guide/memory-provider-plugin.md +++ b/website/docs/developer-guide/memory-provider-plugin.md @@ -342,7 +342,7 @@ data_dir = Path("~/.hermes/my-provider").expanduser() ## Testing -See `tests/agent/test_memory_provider.py` and adjacent memory tests (`tests/agent/test_memory_session_switch.py`, `tests/agent/test_memory_user_id.py`, `tests/run_agent/test_memory_provider_init.py`) for end-to-end patterns. +See `tests/agent/test_memory_provider.py` and adjacent memory tests (`tests/agent/test_memory_session_switch.py`, `tests/agent/test_memory_user_id.py`, `tests/agent/test_memory_provider_init.py`) for end-to-end patterns. ```python from agent.memory_manager import MemoryManager diff --git a/website/docs/developer-guide/provider-runtime.md b/website/docs/developer-guide/provider-runtime.md index 5cb620a6d1..203965c833 100644 --- a/website/docs/developer-guide/provider-runtime.md +++ b/website/docs/developer-guide/provider-runtime.md @@ -211,7 +211,7 @@ Cron jobs **do** support fallback: `run_job()` reads `fallback_providers` (or le Fallback behavior is exercised across several suites: -- `tests/run_agent/test_fallback_credential_isolation.py` — credential isolation between primary and fallback +- `tests/agent/test_fallback_credential_isolation.py` — credential isolation between primary and fallback - `tests/hermes_cli/test_fallback_cmd.py` — the `/fallback` CLI command - `tests/gateway/test_fallback_eviction.py` — gateway eviction of failed providers diff --git a/website/docs/user-guide/cli.md b/website/docs/user-guide/cli.md index 6c4ed6526a..2ca8cf6a70 100644 --- a/website/docs/user-guide/cli.md +++ b/website/docs/user-guide/cli.md @@ -202,7 +202,7 @@ Start a line with `!` to run it as a shell command instead of sending it to the ``` > !git status > !ls -la -> !pytest -x tests/cli +> !pytest -x tests/hermes_cli ``` - **Zero cost.** The model is never invoked — no API call, no tokens, no latency.