code-muse 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code_muse/__init__.py +26 -0
- code_muse/__main__.py +10 -0
- code_muse/agents/__init__.py +31 -0
- code_muse/agents/_builder.py +214 -0
- code_muse/agents/_compaction.py +506 -0
- code_muse/agents/_diagnostics.py +171 -0
- code_muse/agents/_history.py +382 -0
- code_muse/agents/_key_listeners.py +148 -0
- code_muse/agents/_non_streaming_render.py +148 -0
- code_muse/agents/_runtime.py +596 -0
- code_muse/agents/agent_creator_agent.py +603 -0
- code_muse/agents/agent_helios.py +47 -0
- code_muse/agents/agent_manager.py +740 -0
- code_muse/agents/agent_muse.py +78 -0
- code_muse/agents/agent_planning.py +44 -0
- code_muse/agents/agent_qa_melpomene.py +207 -0
- code_muse/agents/base_agent.py +194 -0
- code_muse/agents/event_stream_handler.py +361 -0
- code_muse/agents/json_agent.py +201 -0
- code_muse/agents/prompt_v3.py +521 -0
- code_muse/agents/subagent_stream_handler.py +273 -0
- code_muse/callbacks.py +941 -0
- code_muse/chatgpt_codex_client.py +333 -0
- code_muse/claude_cache_client.py +853 -0
- code_muse/cli_runner/__init__.py +319 -0
- code_muse/cli_runner/args.py +63 -0
- code_muse/cli_runner/loop.py +510 -0
- code_muse/cli_runner/resume.py +72 -0
- code_muse/cli_runner/runner.py +161 -0
- code_muse/command_line/__init__.py +1 -0
- code_muse/command_line/add_model_menu.py +1331 -0
- code_muse/command_line/agent_menu.py +674 -0
- code_muse/command_line/attachments.py +397 -0
- code_muse/command_line/autosave_menu.py +709 -0
- code_muse/command_line/clipboard.py +528 -0
- code_muse/command_line/colors_menu.py +530 -0
- code_muse/command_line/command_handler.py +262 -0
- code_muse/command_line/command_registry.py +150 -0
- code_muse/command_line/config_commands.py +711 -0
- code_muse/command_line/core_commands.py +740 -0
- code_muse/command_line/diff_menu.py +865 -0
- code_muse/command_line/file_path_completion.py +73 -0
- code_muse/command_line/load_context_completion.py +57 -0
- code_muse/command_line/model_picker_completion.py +512 -0
- code_muse/command_line/model_settings_menu.py +983 -0
- code_muse/command_line/onboarding_slides.py +162 -0
- code_muse/command_line/onboarding_wizard.py +337 -0
- code_muse/command_line/pagination.py +41 -0
- code_muse/command_line/pin_command_completion.py +329 -0
- code_muse/command_line/prompt_toolkit_completion.py +886 -0
- code_muse/command_line/session_commands.py +304 -0
- code_muse/command_line/shell_passthrough.py +145 -0
- code_muse/command_line/skills_completion.py +158 -0
- code_muse/command_line/types.py +18 -0
- code_muse/command_line/uc_menu.py +908 -0
- code_muse/command_line/utils.py +105 -0
- code_muse/command_line/wiggum_state.py +77 -0
- code_muse/config.py +1138 -0
- code_muse/config_agent.py +168 -0
- code_muse/config_appearance.py +241 -0
- code_muse/config_model.py +357 -0
- code_muse/config_security.py +73 -0
- code_muse/error_logging.py +132 -0
- code_muse/evals/__init__.py +35 -0
- code_muse/evals/eval_helpers.py +81 -0
- code_muse/evals/eval_runner.py +299 -0
- code_muse/evals/sample_evals/__init__.py +1 -0
- code_muse/evals/sample_evals/eval_frugal_reads.py +59 -0
- code_muse/evals/sample_evals/eval_memory_planning.py +31 -0
- code_muse/evals/sample_evals/eval_shell_efficiency.py +39 -0
- code_muse/evals/sample_evals/eval_tool_masking.py +33 -0
- code_muse/fs_scan_cache/__init__.py +31 -0
- code_muse/fs_scan_cache/invalidation_hooks.py +89 -0
- code_muse/fs_scan_cache/scan_cache_core.cpython-314-darwin.so +0 -0
- code_muse/fs_scan_cache/scan_cache_core.pyx +203 -0
- code_muse/fs_scan_cache/tool_integration.py +309 -0
- code_muse/fs_scan_cache/ttl_policy.py +44 -0
- code_muse/gemini_code_assist.py +383 -0
- code_muse/gemini_model.py +838 -0
- code_muse/hook_engine/README.md +105 -0
- code_muse/hook_engine/__init__.py +21 -0
- code_muse/hook_engine/aliases.py +153 -0
- code_muse/hook_engine/engine.py +221 -0
- code_muse/hook_engine/executor.py +347 -0
- code_muse/hook_engine/matcher.py +154 -0
- code_muse/hook_engine/models.py +245 -0
- code_muse/hook_engine/registry.py +114 -0
- code_muse/hook_engine/trust.py +268 -0
- code_muse/hook_engine/validator.py +144 -0
- code_muse/http_utils.py +360 -0
- code_muse/keymap.py +128 -0
- code_muse/list_filtering.py +26 -0
- code_muse/main.py +10 -0
- code_muse/messaging/__init__.py +259 -0
- code_muse/messaging/bus.py +621 -0
- code_muse/messaging/commands.py +166 -0
- code_muse/messaging/markdown_patches.py +57 -0
- code_muse/messaging/message_queue.py +397 -0
- code_muse/messaging/messages.py +591 -0
- code_muse/messaging/queue_console.py +269 -0
- code_muse/messaging/renderers.py +308 -0
- code_muse/messaging/rich_renderer.py +1158 -0
- code_muse/messaging/shimmer.py +154 -0
- code_muse/messaging/spinner/__init__.py +87 -0
- code_muse/messaging/spinner/console_spinner.py +250 -0
- code_muse/messaging/spinner/spinner_base.py +82 -0
- code_muse/messaging/subagent_console.py +458 -0
- code_muse/model_factory.py +1203 -0
- code_muse/model_switching.py +59 -0
- code_muse/model_utils.py +156 -0
- code_muse/models.json +66 -0
- code_muse/models_cache/__init__.py +26 -0
- code_muse/models_cache/blocking_lru_cache.py +98 -0
- code_muse/models_cache/cache_writer.py +86 -0
- code_muse/models_cache/sha256_hash.cpython-314-darwin.so +0 -0
- code_muse/models_cache/sha256_hash.pyx +34 -0
- code_muse/models_cache/startup_integration.py +75 -0
- code_muse/models_dev_api.json +1 -0
- code_muse/models_dev_parser.py +590 -0
- code_muse/motion.py +126 -0
- code_muse/plugins/__init__.py +471 -0
- code_muse/plugins/agent_skills/__init__.py +32 -0
- code_muse/plugins/agent_skills/config.py +176 -0
- code_muse/plugins/agent_skills/discovery.py +309 -0
- code_muse/plugins/agent_skills/downloader.py +389 -0
- code_muse/plugins/agent_skills/installer.py +19 -0
- code_muse/plugins/agent_skills/metadata.py +293 -0
- code_muse/plugins/agent_skills/prompt_builder.py +66 -0
- code_muse/plugins/agent_skills/register_callbacks.py +298 -0
- code_muse/plugins/agent_skills/remote_catalog.py +320 -0
- code_muse/plugins/agent_skills/skill_catalog.py +254 -0
- code_muse/plugins/agent_skills/skills_install_menu.py +690 -0
- code_muse/plugins/agent_skills/skills_menu.py +791 -0
- code_muse/plugins/autonomous_memory/__init__.py +39 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-darwin.so +0 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.pyx +291 -0
- code_muse/plugins/autonomous_memory/consolidation.py +82 -0
- code_muse/plugins/autonomous_memory/extraction.py +382 -0
- code_muse/plugins/autonomous_memory/lease_lock.py +105 -0
- code_muse/plugins/autonomous_memory/memory_injection.py +59 -0
- code_muse/plugins/autonomous_memory/register_callbacks.py +268 -0
- code_muse/plugins/autonomous_memory/secret_scanner.py +62 -0
- code_muse/plugins/autonomous_memory/session_scanner.py +163 -0
- code_muse/plugins/aws_bedrock/__init__.py +14 -0
- code_muse/plugins/aws_bedrock/config.py +99 -0
- code_muse/plugins/aws_bedrock/register_callbacks.py +241 -0
- code_muse/plugins/aws_bedrock/utils.py +153 -0
- code_muse/plugins/azure_foundry/README.md +238 -0
- code_muse/plugins/azure_foundry/__init__.py +15 -0
- code_muse/plugins/azure_foundry/config.py +125 -0
- code_muse/plugins/azure_foundry/discovery.py +187 -0
- code_muse/plugins/azure_foundry/register_callbacks.py +495 -0
- code_muse/plugins/azure_foundry/token.py +180 -0
- code_muse/plugins/azure_foundry/utils.py +345 -0
- code_muse/plugins/build_filter/__init__.py +1 -0
- code_muse/plugins/build_filter/register_callbacks.py +201 -0
- code_muse/plugins/build_filter/strategies/__init__.py +1 -0
- code_muse/plugins/build_filter/strategies/build.py +397 -0
- code_muse/plugins/chatgpt_oauth/__init__.py +6 -0
- code_muse/plugins/chatgpt_oauth/config.py +52 -0
- code_muse/plugins/chatgpt_oauth/oauth_flow.py +338 -0
- code_muse/plugins/chatgpt_oauth/register_callbacks.py +172 -0
- code_muse/plugins/chatgpt_oauth/test_plugin.py +301 -0
- code_muse/plugins/chatgpt_oauth/utils.py +538 -0
- code_muse/plugins/checkpointing/__init__.py +29 -0
- code_muse/plugins/checkpointing/checkpoint_hook.py +51 -0
- code_muse/plugins/checkpointing/conversation_snapshots.py +117 -0
- code_muse/plugins/checkpointing/register_callbacks.py +51 -0
- code_muse/plugins/checkpointing/restore_command.py +263 -0
- code_muse/plugins/checkpointing/rewind_shortcut.py +88 -0
- code_muse/plugins/checkpointing/shadow_git.py +90 -0
- code_muse/plugins/claude_code_hooks/__init__.py +1 -0
- code_muse/plugins/claude_code_hooks/config.py +188 -0
- code_muse/plugins/claude_code_hooks/register_callbacks.py +208 -0
- code_muse/plugins/claude_code_oauth/README.md +167 -0
- code_muse/plugins/claude_code_oauth/SETUP.md +93 -0
- code_muse/plugins/claude_code_oauth/__init__.py +25 -0
- code_muse/plugins/claude_code_oauth/config.py +52 -0
- code_muse/plugins/claude_code_oauth/fast_mode.py +124 -0
- code_muse/plugins/claude_code_oauth/prompt_handler.py +63 -0
- code_muse/plugins/claude_code_oauth/register_callbacks.py +547 -0
- code_muse/plugins/claude_code_oauth/test_fast_mode.py +165 -0
- code_muse/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_muse/plugins/claude_code_oauth/token_refresh_heartbeat.py +237 -0
- code_muse/plugins/claude_code_oauth/utils.py +664 -0
- code_muse/plugins/copilot_auth/__init__.py +11 -0
- code_muse/plugins/copilot_auth/config.py +91 -0
- code_muse/plugins/copilot_auth/reasoning_client.py +409 -0
- code_muse/plugins/copilot_auth/register_callbacks.py +461 -0
- code_muse/plugins/copilot_auth/utils.py +584 -0
- code_muse/plugins/custom_commands/__init__.py +14 -0
- code_muse/plugins/custom_commands/args_injection.py +82 -0
- code_muse/plugins/custom_commands/command_discovery.py +89 -0
- code_muse/plugins/custom_commands/command_toml_schema.py +71 -0
- code_muse/plugins/custom_commands/register_callbacks.py +176 -0
- code_muse/plugins/customizable_commands/__init__.py +0 -0
- code_muse/plugins/customizable_commands/register_callbacks.py +136 -0
- code_muse/plugins/destructive_command_guard/__init__.py +14 -0
- code_muse/plugins/destructive_command_guard/detector.py +375 -0
- code_muse/plugins/destructive_command_guard/register_callbacks.py +148 -0
- code_muse/plugins/example_custom_command/README.md +280 -0
- code_muse/plugins/example_custom_command/register_callbacks.py +51 -0
- code_muse/plugins/file_permission_handler/__init__.py +4 -0
- code_muse/plugins/file_permission_handler/register_callbacks.py +441 -0
- code_muse/plugins/filter_engine/__init__.py +30 -0
- code_muse/plugins/filter_engine/classifier.py +153 -0
- code_muse/plugins/filter_engine/content_detector.py +184 -0
- code_muse/plugins/filter_engine/dispatcher.py +244 -0
- code_muse/plugins/filter_engine/register_callbacks.py +188 -0
- code_muse/plugins/filter_engine/registry.py +279 -0
- code_muse/plugins/filter_engine/strategies/__init__.py +8 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.pyx +348 -0
- code_muse/plugins/filter_engine/strategies/ast_parser.py +167 -0
- code_muse/plugins/filter_engine/strategies/code.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/code.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/code.pyx +584 -0
- code_muse/plugins/filter_engine/strategies/git.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/git.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/git.pyx +438 -0
- code_muse/plugins/filter_engine/strategies/json_compressor.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/json_compressor.pyx +253 -0
- code_muse/plugins/filter_engine/strategies/json_patterns.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/json_patterns.pyx +178 -0
- code_muse/plugins/filter_engine/strategies/lint.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/lint.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/lint.pyx +626 -0
- code_muse/plugins/filter_engine/strategies/test.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/test.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/test.pyx +431 -0
- code_muse/plugins/filter_engine/verbosity.py +63 -0
- code_muse/plugins/force_push_guard/__init__.py +5 -0
- code_muse/plugins/force_push_guard/detector.py +96 -0
- code_muse/plugins/force_push_guard/register_callbacks.py +144 -0
- code_muse/plugins/force_push_guard/test_detector.py +143 -0
- code_muse/plugins/frontend_emitter/__init__.py +25 -0
- code_muse/plugins/frontend_emitter/emitter.py +121 -0
- code_muse/plugins/frontend_emitter/register_callbacks.py +259 -0
- code_muse/plugins/gac/__init__.py +4 -0
- code_muse/plugins/gac/git_ops.py +136 -0
- code_muse/plugins/gac/prompt.py +191 -0
- code_muse/plugins/gac/register_callbacks.py +82 -0
- code_muse/plugins/hook_creator/__init__.py +1 -0
- code_muse/plugins/hook_creator/register_callbacks.py +34 -0
- code_muse/plugins/hook_manager/__init__.py +1 -0
- code_muse/plugins/hook_manager/config.py +289 -0
- code_muse/plugins/hook_manager/hooks_menu.py +563 -0
- code_muse/plugins/hook_manager/register_callbacks.py +227 -0
- code_muse/plugins/hook_monitor/register_callbacks.py +36 -0
- code_muse/plugins/mindpack/__init__.py +0 -0
- code_muse/plugins/mindpack/factory.py +930 -0
- code_muse/plugins/mindpack/judge.py +573 -0
- code_muse/plugins/mindpack/memory.py +100 -0
- code_muse/plugins/mindpack/mindpack_menu.py +1552 -0
- code_muse/plugins/mindpack/orchestration.py +605 -0
- code_muse/plugins/mindpack/register_callbacks.py +175 -0
- code_muse/plugins/mindpack/schemas.py +358 -0
- code_muse/plugins/mindpack/tools.py +387 -0
- code_muse/plugins/oauth_muse_html.py +226 -0
- code_muse/plugins/ollama_setup/__init__.py +5 -0
- code_muse/plugins/ollama_setup/completer.py +36 -0
- code_muse/plugins/ollama_setup/register_callbacks.py +410 -0
- code_muse/plugins/plan_command/__init__.py +0 -0
- code_muse/plugins/plan_command/register_callbacks.py +206 -0
- code_muse/plugins/plan_mode/__init__.py +37 -0
- code_muse/plugins/plan_mode/mode_cycling.py +40 -0
- code_muse/plugins/plan_mode/plan_generation.py +68 -0
- code_muse/plugins/plan_mode/plan_hooks.py +74 -0
- code_muse/plugins/plan_mode/plan_mode_tools.py +138 -0
- code_muse/plugins/plan_mode/register_callbacks.py +121 -0
- code_muse/plugins/plugin_trust/register_callbacks.py +140 -0
- code_muse/plugins/policy_engine/__init__.py +46 -0
- code_muse/plugins/policy_engine/approval_flow_integration.py +59 -0
- code_muse/plugins/policy_engine/policy_evaluator.py +75 -0
- code_muse/plugins/policy_engine/policy_file_discovery.py +90 -0
- code_muse/plugins/policy_engine/policy_toml_schema.py +115 -0
- code_muse/plugins/policy_engine/register_callbacks.py +112 -0
- code_muse/plugins/pop_command/__init__.py +1 -0
- code_muse/plugins/pop_command/register_callbacks.py +189 -0
- code_muse/plugins/prompt_newline/__init__.py +13 -0
- code_muse/plugins/prompt_newline/config.py +19 -0
- code_muse/plugins/prompt_newline/register_callbacks.py +159 -0
- code_muse/plugins/safety_status/__init__.py +0 -0
- code_muse/plugins/safety_status/register_callbacks.py +113 -0
- code_muse/plugins/semantic_compression/__init__.py +6 -0
- code_muse/plugins/semantic_compression/compressor.py +295 -0
- code_muse/plugins/semantic_compression/config.py +123 -0
- code_muse/plugins/semantic_compression/register_callbacks.py +320 -0
- code_muse/plugins/shell_minimizer/__init__.py +50 -0
- code_muse/plugins/shell_minimizer/builtin_filters.toml +393 -0
- code_muse/plugins/shell_minimizer/pipeline.py +556 -0
- code_muse/plugins/shell_minimizer/primitives.py +482 -0
- code_muse/plugins/shell_minimizer/register_callbacks.py +276 -0
- code_muse/plugins/shell_safety/__init__.py +6 -0
- code_muse/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_muse/plugins/shell_safety/command_cache.py +149 -0
- code_muse/plugins/shell_safety/register_callbacks.py +202 -0
- code_muse/plugins/synthetic_status/__init__.py +1 -0
- code_muse/plugins/synthetic_status/register_callbacks.py +128 -0
- code_muse/plugins/synthetic_status/status_api.py +145 -0
- code_muse/plugins/token_caching/__init__.py +21 -0
- code_muse/plugins/token_caching/cache_hit_tracking.py +128 -0
- code_muse/plugins/token_caching/cacheable_prefix_detection.py +28 -0
- code_muse/plugins/token_caching/register_callbacks.py +54 -0
- code_muse/plugins/token_caching/stats_display.py +35 -0
- code_muse/plugins/token_tracking/__init__.py +26 -0
- code_muse/plugins/token_tracking/database.py +381 -0
- code_muse/plugins/token_tracking/edit_analyzer.py +97 -0
- code_muse/plugins/token_tracking/record.py +55 -0
- code_muse/plugins/token_tracking/register_callbacks.py +277 -0
- code_muse/plugins/token_tracking/reports.py +329 -0
- code_muse/plugins/universal_constructor/__init__.py +13 -0
- code_muse/plugins/universal_constructor/models.py +136 -0
- code_muse/plugins/universal_constructor/register_callbacks.py +47 -0
- code_muse/plugins/universal_constructor/registry.py +390 -0
- code_muse/plugins/universal_constructor/runner.py +474 -0
- code_muse/plugins/universal_constructor/safety.py +440 -0
- code_muse/plugins/universal_constructor/sandbox.py +584 -0
- code_muse/provider_identity.py +105 -0
- code_muse/pydantic_patches.py +410 -0
- code_muse/reopenable_async_client.py +233 -0
- code_muse/round_robin_model.py +151 -0
- code_muse/secret_storage.py +74 -0
- code_muse/security/__init__.py +1 -0
- code_muse/security/redaction.cpython-314-darwin.so +0 -0
- code_muse/security/redaction.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/security/redaction.pyx +135 -0
- code_muse/session_storage.py +565 -0
- code_muse/status_display.py +261 -0
- code_muse/stream_parser/__init__.py +76 -0
- code_muse/stream_parser/assistant_text_parser.py +90 -0
- code_muse/stream_parser/citation_parser.py +76 -0
- code_muse/stream_parser/inline_hidden_tag_parser.py +236 -0
- code_muse/stream_parser/proposed_plan_parser.py +158 -0
- code_muse/stream_parser/stream_text_chunk.py +23 -0
- code_muse/stream_parser/stream_text_parser.py +27 -0
- code_muse/stream_parser/tagged_line_parser.cpython-314-darwin.so +0 -0
- code_muse/stream_parser/tagged_line_parser.pyx +251 -0
- code_muse/stream_parser/utf8_stream_parser.cpython-314-darwin.so +0 -0
- code_muse/stream_parser/utf8_stream_parser.pyx +206 -0
- code_muse/summarization_agent.py +308 -0
- code_muse/terminal_utils.cpython-314-darwin.so +0 -0
- code_muse/terminal_utils.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/terminal_utils.pyx +483 -0
- code_muse/tools/__init__.py +459 -0
- code_muse/tools/agent_tools.py +613 -0
- code_muse/tools/ask_user_question/__init__.py +26 -0
- code_muse/tools/ask_user_question/constants.py +73 -0
- code_muse/tools/ask_user_question/demo_tui.py +55 -0
- code_muse/tools/ask_user_question/handler.py +232 -0
- code_muse/tools/ask_user_question/models.py +302 -0
- code_muse/tools/ask_user_question/registration.py +37 -0
- code_muse/tools/ask_user_question/renderers.py +336 -0
- code_muse/tools/ask_user_question/terminal_ui.py +327 -0
- code_muse/tools/ask_user_question/theme.py +156 -0
- code_muse/tools/ask_user_question/tui_loop.py +422 -0
- code_muse/tools/background_jobs.py +99 -0
- code_muse/tools/browser/__init__.py +37 -0
- code_muse/tools/browser/browser_control.py +289 -0
- code_muse/tools/browser/browser_interactions.py +545 -0
- code_muse/tools/browser/browser_locators.py +640 -0
- code_muse/tools/browser/browser_manager.py +376 -0
- code_muse/tools/browser/browser_navigation.py +251 -0
- code_muse/tools/browser/browser_screenshot.py +180 -0
- code_muse/tools/browser/browser_scripts.py +462 -0
- code_muse/tools/browser/browser_workflows.py +222 -0
- code_muse/tools/chrome_cdp/__init__.py +1070 -0
- code_muse/tools/chrome_cdp/register_callbacks.py +61 -0
- code_muse/tools/command_runner.py +1401 -0
- code_muse/tools/common.py +1407 -0
- code_muse/tools/display.py +87 -0
- code_muse/tools/file_modifications.py +1099 -0
- code_muse/tools/file_operations.py +860 -0
- code_muse/tools/image_tools.py +185 -0
- code_muse/tools/meetin_proxy/__init__.py +243 -0
- code_muse/tools/meetin_proxy/capture_addon.py +82 -0
- code_muse/tools/meetin_proxy/proxy_manager.py +326 -0
- code_muse/tools/meetin_proxy/register_callbacks.py +45 -0
- code_muse/tools/path_policy.py +219 -0
- code_muse/tools/skills_tools.py +586 -0
- code_muse/tools/subagent_context.py +158 -0
- code_muse/tools/tools_content.py +50 -0
- code_muse/tools/universal_constructor.py +965 -0
- code_muse/uvx_detection.py +241 -0
- code_muse/version_checker.py +86 -0
- code_muse-0.0.1.data/data/code_muse/models.json +66 -0
- code_muse-0.0.1.data/data/code_muse/models_dev_api.json +1 -0
- code_muse-0.0.1.dist-info/METADATA +845 -0
- code_muse-0.0.1.dist-info/RECORD +394 -0
- code_muse-0.0.1.dist-info/WHEEL +4 -0
- code_muse-0.0.1.dist-info/entry_points.txt +2 -0
- code_muse-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
# cython: language_level=3
|
|
2
|
+
"""Test compression strategies for the filter engine.
|
|
3
|
+
|
|
4
|
+
Supports pytest, vitest/jest, cargo test, and generic test runners.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from code_muse.plugins.filter_engine.registry import get_registry
|
|
12
|
+
from code_muse.plugins.filter_engine.verbosity import VerbosityLevel
|
|
13
|
+
from code_muse.tools.command_runner import ShellCommandOutput
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Pre-compiled regex patterns used in the hot loops.
|
|
17
|
+
_pytest_result_pattern = re.compile(
|
|
18
|
+
r"(\S+)\s+(PASSED|FAILED|ERROR|SKIPPED|XFAIL|XPASS)"
|
|
19
|
+
)
|
|
20
|
+
_failure_start_pattern = re.compile(r"^(FAILED |ERROR )")
|
|
21
|
+
_generic_passed_pattern = re.compile(r"\S+\s+(PASSED|SKIPPED)")
|
|
22
|
+
_vitest_summary_pattern = re.compile(r"Tests?\s+\d+\s+passed|failed", re.IGNORECASE)
|
|
23
|
+
_dispatcher_pytest_pattern = re.compile(r"pytest\b|python\s+-m\s+pytest\b")
|
|
24
|
+
_dispatcher_vitest_pattern = re.compile(r"vitest\b|jest\b|npx\s+(jest|vitest)\b")
|
|
25
|
+
_dispatcher_cargo_pattern = re.compile(r"cargo\s+test\b")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _extract_pytest_summary(lines: list[str]) -> str:
|
|
29
|
+
"""Extract the pytest summary line (e.g. ``= 2 passed, 1 failed in 0.5s =``)."""
|
|
30
|
+
cdef str line
|
|
31
|
+
for line in reversed(lines):
|
|
32
|
+
if line.strip().startswith("=") and "passed" in line:
|
|
33
|
+
return line.strip()
|
|
34
|
+
return ""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def compress_pytest(
|
|
38
|
+
stdout: str,
|
|
39
|
+
stderr: str,
|
|
40
|
+
verbosity: VerbosityLevel,
|
|
41
|
+
) -> ShellCommandOutput:
|
|
42
|
+
"""Compress pytest text output.
|
|
43
|
+
|
|
44
|
+
State-machine style parser that tracks PASS/FAIL/SKIP/ERROR/XFAIL/XPASS.
|
|
45
|
+
|
|
46
|
+
* Compact (default): show FAILURES + final summary line.
|
|
47
|
+
* Verbose: show all results with filenames.
|
|
48
|
+
* Very-verbose / raw: full output (handled by caller before invocation).
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
stdout: Raw pytest stdout.
|
|
52
|
+
stderr: Raw pytest stderr.
|
|
53
|
+
verbosity: Current verbosity level.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Compressed :class:`ShellCommandOutput`.
|
|
57
|
+
"""
|
|
58
|
+
cdef list lines = stdout.splitlines()
|
|
59
|
+
cdef str summary = _extract_pytest_summary(lines)
|
|
60
|
+
|
|
61
|
+
cdef list failures = []
|
|
62
|
+
cdef list all_results = []
|
|
63
|
+
|
|
64
|
+
cdef bint in_failure = False
|
|
65
|
+
cdef list current_failure = []
|
|
66
|
+
|
|
67
|
+
cdef str line
|
|
68
|
+
cdef str stripped
|
|
69
|
+
cdef object result_match
|
|
70
|
+
|
|
71
|
+
for line in lines:
|
|
72
|
+
stripped = line.rstrip("\r")
|
|
73
|
+
|
|
74
|
+
# Detect test result lines
|
|
75
|
+
result_match = _pytest_result_pattern.search(stripped)
|
|
76
|
+
if result_match:
|
|
77
|
+
all_results.append(stripped)
|
|
78
|
+
if result_match.group(2) in ("FAILED", "ERROR"):
|
|
79
|
+
failures.append(stripped)
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
# Collect failure detail blocks
|
|
83
|
+
if _failure_start_pattern.match(stripped):
|
|
84
|
+
in_failure = True
|
|
85
|
+
current_failure = [stripped]
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
if in_failure:
|
|
89
|
+
if stripped.startswith("=") or _generic_passed_pattern.search(stripped):
|
|
90
|
+
# End of failure block
|
|
91
|
+
if current_failure:
|
|
92
|
+
failures.append("\n".join(current_failure))
|
|
93
|
+
in_failure = False
|
|
94
|
+
current_failure = []
|
|
95
|
+
else:
|
|
96
|
+
current_failure.append(stripped)
|
|
97
|
+
|
|
98
|
+
if in_failure and current_failure:
|
|
99
|
+
failures.append("\n".join(current_failure))
|
|
100
|
+
|
|
101
|
+
cdef str compressed
|
|
102
|
+
cdef list parts
|
|
103
|
+
if verbosity >= VerbosityLevel.VERBOSE:
|
|
104
|
+
parts = all_results if all_results else lines
|
|
105
|
+
if summary:
|
|
106
|
+
parts.append(f"\n{summary}")
|
|
107
|
+
compressed = "\n".join(parts)
|
|
108
|
+
else:
|
|
109
|
+
parts = failures if failures else [summary] if summary else lines[:5]
|
|
110
|
+
if summary and summary not in parts:
|
|
111
|
+
parts.append(summary)
|
|
112
|
+
compressed = "\n".join(parts)
|
|
113
|
+
|
|
114
|
+
if not compressed.strip():
|
|
115
|
+
compressed = stdout[:512] # fallback: first 512 chars
|
|
116
|
+
|
|
117
|
+
cdef bint has_error = bool(stderr.strip())
|
|
118
|
+
return ShellCommandOutput(
|
|
119
|
+
success=not has_error,
|
|
120
|
+
command="pytest",
|
|
121
|
+
stdout=compressed,
|
|
122
|
+
stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
|
|
123
|
+
exit_code=0 if not has_error else 1,
|
|
124
|
+
execution_time=None,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def compress_vitest_jest(
|
|
129
|
+
stdout: str,
|
|
130
|
+
stderr: str,
|
|
131
|
+
verbosity: VerbosityLevel,
|
|
132
|
+
) -> ShellCommandOutput:
|
|
133
|
+
"""Compress vitest/jest output.
|
|
134
|
+
|
|
135
|
+
Attempts JSON parsing first, then falls back to text parsing.
|
|
136
|
+
Failures are always surfaced; passes are hidden at compact levels.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
stdout: Raw stdout.
|
|
140
|
+
stderr: Raw stderr.
|
|
141
|
+
verbosity: Current verbosity level.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Compressed :class:`ShellCommandOutput`.
|
|
145
|
+
"""
|
|
146
|
+
# Try JSON mode first
|
|
147
|
+
try:
|
|
148
|
+
data = json.loads(stdout)
|
|
149
|
+
if isinstance(data, dict) and "testResults" in data:
|
|
150
|
+
return _compress_jest_json(data, stderr, verbosity)
|
|
151
|
+
except ValueError:
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
# Fallback to text parsing
|
|
155
|
+
cdef list lines = stdout.splitlines()
|
|
156
|
+
cdef list failures = []
|
|
157
|
+
cdef str summary = ""
|
|
158
|
+
|
|
159
|
+
cdef str line
|
|
160
|
+
cdef str stripped
|
|
161
|
+
|
|
162
|
+
for line in lines:
|
|
163
|
+
stripped = line.rstrip("\r")
|
|
164
|
+
if "FAIL" in stripped or "✕" in stripped:
|
|
165
|
+
failures.append(stripped)
|
|
166
|
+
if _vitest_summary_pattern.search(stripped):
|
|
167
|
+
summary = stripped
|
|
168
|
+
|
|
169
|
+
cdef str compressed
|
|
170
|
+
if verbosity >= VerbosityLevel.VERBOSE:
|
|
171
|
+
compressed = stdout
|
|
172
|
+
else:
|
|
173
|
+
parts = (
|
|
174
|
+
failures + [summary]
|
|
175
|
+
if failures and summary
|
|
176
|
+
else failures
|
|
177
|
+
if failures
|
|
178
|
+
else [summary]
|
|
179
|
+
if summary
|
|
180
|
+
else lines[:5]
|
|
181
|
+
)
|
|
182
|
+
compressed = "\n".join(parts)
|
|
183
|
+
|
|
184
|
+
cdef bint has_error = bool(failures) or bool(stderr.strip())
|
|
185
|
+
return ShellCommandOutput(
|
|
186
|
+
success=not has_error,
|
|
187
|
+
command="vitest/jest",
|
|
188
|
+
stdout=compressed,
|
|
189
|
+
stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
|
|
190
|
+
exit_code=0 if not has_error else 1,
|
|
191
|
+
execution_time=None,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _compress_jest_json(
|
|
196
|
+
data: dict[str, Any],
|
|
197
|
+
stderr: str,
|
|
198
|
+
verbosity: VerbosityLevel,
|
|
199
|
+
) -> ShellCommandOutput:
|
|
200
|
+
"""Compress jest JSON output.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
data: Parsed JSON test results.
|
|
204
|
+
stderr: Raw stderr.
|
|
205
|
+
verbosity: Current verbosity level.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Compressed :class:`ShellCommandOutput`.
|
|
209
|
+
"""
|
|
210
|
+
cdef list results = data.get("testResults", [])
|
|
211
|
+
cdef list failures = []
|
|
212
|
+
cdef int total_tests = 0
|
|
213
|
+
cdef int passed_tests = 0
|
|
214
|
+
cdef int failed_tests = 0
|
|
215
|
+
|
|
216
|
+
cdef dict suite
|
|
217
|
+
cdef list assertions
|
|
218
|
+
cdef dict test
|
|
219
|
+
cdef str status
|
|
220
|
+
cdef str title
|
|
221
|
+
|
|
222
|
+
for suite in results:
|
|
223
|
+
assertions = suite.get("assertionResults", [])
|
|
224
|
+
for test in assertions:
|
|
225
|
+
total_tests += 1
|
|
226
|
+
status = test.get("status", "")
|
|
227
|
+
title = test.get("title", "unknown")
|
|
228
|
+
if status == "passed":
|
|
229
|
+
passed_tests += 1
|
|
230
|
+
else:
|
|
231
|
+
failed_tests += 1
|
|
232
|
+
failures.append(f" FAIL {title}")
|
|
233
|
+
|
|
234
|
+
cdef str summary = f"{total_tests} tests, {passed_tests} passed, {failed_tests} failed"
|
|
235
|
+
|
|
236
|
+
cdef str compressed
|
|
237
|
+
if verbosity >= VerbosityLevel.VERBOSE:
|
|
238
|
+
compressed = summary + "\n" + "\n".join(failures)
|
|
239
|
+
else:
|
|
240
|
+
parts = failures + [summary] if failures else [summary]
|
|
241
|
+
compressed = "\n".join(parts)
|
|
242
|
+
|
|
243
|
+
return ShellCommandOutput(
|
|
244
|
+
success=failed_tests == 0,
|
|
245
|
+
command="jest",
|
|
246
|
+
stdout=compressed,
|
|
247
|
+
stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
|
|
248
|
+
exit_code=0 if failed_tests == 0 else 1,
|
|
249
|
+
execution_time=None,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def compress_cargo_test(
|
|
254
|
+
stdout: str,
|
|
255
|
+
stderr: str,
|
|
256
|
+
verbosity: VerbosityLevel,
|
|
257
|
+
) -> ShellCommandOutput:
|
|
258
|
+
"""Compress ``cargo test`` output.
|
|
259
|
+
|
|
260
|
+
Parses both text and NDJSON (``--message-format=json``) output.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
stdout: Raw stdout.
|
|
264
|
+
stderr: Raw stderr.
|
|
265
|
+
verbosity: Current verbosity level.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Compressed :class:`ShellCommandOutput`.
|
|
269
|
+
"""
|
|
270
|
+
# Try NDJSON first
|
|
271
|
+
cdef list json_lines = [line for line in stdout.splitlines() if line.strip().startswith("{")]
|
|
272
|
+
if json_lines:
|
|
273
|
+
return _compress_cargo_ndjson(json_lines, stderr, verbosity)
|
|
274
|
+
|
|
275
|
+
# Text parsing
|
|
276
|
+
cdef list lines = stdout.splitlines()
|
|
277
|
+
cdef list failures = []
|
|
278
|
+
cdef str summary = ""
|
|
279
|
+
|
|
280
|
+
cdef str line
|
|
281
|
+
cdef str stripped
|
|
282
|
+
|
|
283
|
+
for line in lines:
|
|
284
|
+
stripped = line.rstrip("\r")
|
|
285
|
+
if stripped.startswith("test result:"):
|
|
286
|
+
summary = stripped
|
|
287
|
+
elif "FAILED" in stripped and stripped.startswith("test "):
|
|
288
|
+
failures.append(stripped)
|
|
289
|
+
|
|
290
|
+
cdef str compressed
|
|
291
|
+
if verbosity >= VerbosityLevel.VERBOSE:
|
|
292
|
+
compressed = stdout
|
|
293
|
+
else:
|
|
294
|
+
parts = failures if failures else [summary] if summary else lines[:5]
|
|
295
|
+
if summary and summary not in parts:
|
|
296
|
+
parts.append(summary)
|
|
297
|
+
compressed = "\n".join(parts)
|
|
298
|
+
|
|
299
|
+
cdef bint has_error = bool(failures) or bool(stderr.strip())
|
|
300
|
+
return ShellCommandOutput(
|
|
301
|
+
success=not has_error,
|
|
302
|
+
command="cargo test",
|
|
303
|
+
stdout=compressed,
|
|
304
|
+
stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
|
|
305
|
+
exit_code=0 if not has_error else 1,
|
|
306
|
+
execution_time=None,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _compress_cargo_ndjson(
|
|
311
|
+
json_lines: list[str],
|
|
312
|
+
stderr: str,
|
|
313
|
+
verbosity: VerbosityLevel,
|
|
314
|
+
) -> ShellCommandOutput:
|
|
315
|
+
"""Compress cargo NDJSON output.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
json_lines: Lines that look like JSON objects.
|
|
319
|
+
stderr: Raw stderr.
|
|
320
|
+
verbosity: Current verbosity level.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
Compressed :class:`ShellCommandOutput`.
|
|
324
|
+
"""
|
|
325
|
+
cdef list failures = []
|
|
326
|
+
cdef int total = 0
|
|
327
|
+
cdef int passed = 0
|
|
328
|
+
cdef int failed = 0
|
|
329
|
+
|
|
330
|
+
cdef str line
|
|
331
|
+
cdef dict obj
|
|
332
|
+
cdef str event
|
|
333
|
+
cdef str name
|
|
334
|
+
|
|
335
|
+
for line in json_lines:
|
|
336
|
+
try:
|
|
337
|
+
obj = json.loads(line)
|
|
338
|
+
event = obj.get("event", "")
|
|
339
|
+
if event == "started":
|
|
340
|
+
total += 1
|
|
341
|
+
elif event == "ok":
|
|
342
|
+
passed += 1
|
|
343
|
+
elif event in ("failed", "error"):
|
|
344
|
+
failed += 1
|
|
345
|
+
name = obj.get("name", "unknown")
|
|
346
|
+
failures.append(f" FAIL {name}")
|
|
347
|
+
except ValueError:
|
|
348
|
+
continue
|
|
349
|
+
|
|
350
|
+
cdef str summary = f"{total} tests, {passed} passed, {failed} failed"
|
|
351
|
+
|
|
352
|
+
cdef str compressed
|
|
353
|
+
if verbosity >= VerbosityLevel.VERBOSE:
|
|
354
|
+
compressed = summary + "\n" + "\n".join(failures)
|
|
355
|
+
else:
|
|
356
|
+
parts = failures + [summary] if failures else [summary]
|
|
357
|
+
compressed = "\n".join(parts)
|
|
358
|
+
|
|
359
|
+
return ShellCommandOutput(
|
|
360
|
+
success=failed == 0,
|
|
361
|
+
command="cargo test",
|
|
362
|
+
stdout=compressed,
|
|
363
|
+
stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
|
|
364
|
+
exit_code=0 if failed == 0 else 1,
|
|
365
|
+
execution_time=None,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def compress_test(
|
|
370
|
+
command: str,
|
|
371
|
+
stdout: str,
|
|
372
|
+
stderr: str,
|
|
373
|
+
exit_code: int,
|
|
374
|
+
verbosity: VerbosityLevel,
|
|
375
|
+
) -> ShellCommandOutput | None:
|
|
376
|
+
"""Main dispatcher for test compression strategies.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
command: The original test command.
|
|
380
|
+
stdout: Raw stdout.
|
|
381
|
+
stderr: Raw stderr.
|
|
382
|
+
exit_code: Process exit code.
|
|
383
|
+
verbosity: Current verbosity level.
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
A compressed :class:`ShellCommandOutput` or ``None``.
|
|
387
|
+
"""
|
|
388
|
+
cdef str stripped = command.strip()
|
|
389
|
+
cdef object out
|
|
390
|
+
|
|
391
|
+
if _dispatcher_pytest_pattern.search(stripped):
|
|
392
|
+
out = compress_pytest(stdout, stderr, verbosity)
|
|
393
|
+
out.exit_code = exit_code
|
|
394
|
+
return out
|
|
395
|
+
|
|
396
|
+
if _dispatcher_vitest_pattern.search(stripped):
|
|
397
|
+
out = compress_vitest_jest(stdout, stderr, verbosity)
|
|
398
|
+
out.exit_code = exit_code
|
|
399
|
+
return out
|
|
400
|
+
|
|
401
|
+
if _dispatcher_cargo_pattern.search(stripped):
|
|
402
|
+
out = compress_cargo_test(stdout, stderr, verbosity)
|
|
403
|
+
out.exit_code = exit_code
|
|
404
|
+
return out
|
|
405
|
+
|
|
406
|
+
# Generic fallback: keep failures + summary heuristic
|
|
407
|
+
cdef list lines = stdout.splitlines()
|
|
408
|
+
cdef list failures = [line for line in lines if "FAIL" in line or "failed" in line.lower()]
|
|
409
|
+
cdef str summary = next((line for line in reversed(lines) if "passed" in line.lower()), "")
|
|
410
|
+
|
|
411
|
+
cdef str compressed
|
|
412
|
+
if verbosity >= VerbosityLevel.VERBOSE:
|
|
413
|
+
compressed = stdout
|
|
414
|
+
else:
|
|
415
|
+
parts = failures if failures else [summary] if summary else lines[:5]
|
|
416
|
+
compressed = "\n".join(parts)
|
|
417
|
+
|
|
418
|
+
return ShellCommandOutput(
|
|
419
|
+
success=exit_code == 0,
|
|
420
|
+
command=stripped,
|
|
421
|
+
stdout=compressed,
|
|
422
|
+
stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
|
|
423
|
+
exit_code=exit_code,
|
|
424
|
+
execution_time=None,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
# ---------------------------------------------------------------------------
|
|
429
|
+
# Register with the strategy registry
|
|
430
|
+
# ---------------------------------------------------------------------------
|
|
431
|
+
get_registry().register("test", compress_test, priority=0)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Verbosity configuration for the filter engine.
|
|
2
|
+
|
|
3
|
+
Defines verbosity levels and provides helpers to read the current level
|
|
4
|
+
from CLI flags or the ``FAST_PUPPY_VERBOSITY`` environment variable.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from enum import IntEnum
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class VerbosityLevel(IntEnum):
|
|
13
|
+
"""Verbosity levels for filter output compression.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
ULTRA_COMPACT: Minimal output — single-line summaries only.
|
|
17
|
+
COMPACT: Default — concise summaries with key counts.
|
|
18
|
+
VERBOSE: Include file lists and short excerpts.
|
|
19
|
+
VERY_VERBOSE: Include full output sections.
|
|
20
|
+
RAW: Disable all filtering — passthrough raw output.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
ULTRA_COMPACT = 0 # -u
|
|
24
|
+
COMPACT = 1 # default (no flag)
|
|
25
|
+
VERBOSE = 2 # -v
|
|
26
|
+
VERY_VERBOSE = 3 # -vv
|
|
27
|
+
RAW = 4 # -vvv
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_verbosity() -> VerbosityLevel:
|
|
31
|
+
"""Determine the current verbosity level.
|
|
32
|
+
|
|
33
|
+
Resolution order:
|
|
34
|
+
|
|
35
|
+
1. Check ``sys.argv`` for ``-u``/``--ultra-compact``, ``-v``, ``-vv``, ``-vvv``.
|
|
36
|
+
2. Check the ``FAST_PUPPY_VERBOSITY`` environment variable (``0``–``4``).
|
|
37
|
+
3. Default to ``VerbosityLevel.COMPACT``.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
The resolved verbosity level.
|
|
41
|
+
"""
|
|
42
|
+
# CLI flag parsing
|
|
43
|
+
args = sys.argv
|
|
44
|
+
if "-u" in args or "--ultra-compact" in args:
|
|
45
|
+
return VerbosityLevel.ULTRA_COMPACT
|
|
46
|
+
if "-vvv" in args:
|
|
47
|
+
return VerbosityLevel.RAW
|
|
48
|
+
if "-vv" in args:
|
|
49
|
+
return VerbosityLevel.VERY_VERBOSE
|
|
50
|
+
if "-v" in args:
|
|
51
|
+
return VerbosityLevel.VERBOSE
|
|
52
|
+
|
|
53
|
+
# Environment variable
|
|
54
|
+
env_val = os.environ.get("FAST_PUPPY_VERBOSITY")
|
|
55
|
+
if env_val is not None:
|
|
56
|
+
try:
|
|
57
|
+
level = int(env_val)
|
|
58
|
+
if 0 <= level <= 4:
|
|
59
|
+
return VerbosityLevel(level)
|
|
60
|
+
except ValueError:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
return VerbosityLevel.COMPACT
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Pattern detection for git force push commands.
|
|
2
|
+
|
|
3
|
+
Detects force push patterns in shell commands, covering all the sneaky
|
|
4
|
+
ways git lets you wreck a remote branch.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ForcePushMatch:
|
|
13
|
+
"""Result of a force push pattern match."""
|
|
14
|
+
|
|
15
|
+
pattern_name: str
|
|
16
|
+
description: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Matches shell operators that precede a new command in a pipeline/chain.
|
|
20
|
+
# E.g. "cd foo && git push --force" or "true || git push -f"
|
|
21
|
+
_SHELL_OPERATOR_RE = re.compile(r"(?:^|&&|\|\||;|\|)\s*git\s+push\b", re.MULTILINE)
|
|
22
|
+
|
|
23
|
+
# Ordered by specificity — first match wins.
|
|
24
|
+
# Each tuple: (compiled regex, human-readable name, what it catches)
|
|
25
|
+
_FORCE_PUSH_PATTERNS: list[tuple[re.Pattern, str, str]] = [
|
|
26
|
+
(
|
|
27
|
+
re.compile(r"\bgit\s+push\b.*--force-with-lease"),
|
|
28
|
+
"--force-with-lease",
|
|
29
|
+
"force push with lease (safer, but still rewrites history)",
|
|
30
|
+
),
|
|
31
|
+
(
|
|
32
|
+
re.compile(r"\bgit\s+push\b.*--force-if-includes"),
|
|
33
|
+
"--force-if-includes",
|
|
34
|
+
"force push with includes check (still rewrites history)",
|
|
35
|
+
),
|
|
36
|
+
(
|
|
37
|
+
re.compile(r"\bgit\s+push\b.*--force"),
|
|
38
|
+
"--force",
|
|
39
|
+
"force push (rewrites remote history)",
|
|
40
|
+
),
|
|
41
|
+
(
|
|
42
|
+
re.compile(r"\bgit\s+push\b.*\s-f\b"),
|
|
43
|
+
"-f",
|
|
44
|
+
"force push shorthand (rewrites remote history)",
|
|
45
|
+
),
|
|
46
|
+
(
|
|
47
|
+
re.compile(r"\bgit\s+push\b.*\s-F\b"),
|
|
48
|
+
"-F",
|
|
49
|
+
"force push shorthand (rewrites remote history)",
|
|
50
|
+
),
|
|
51
|
+
# The +refspec syntax: git push origin +main, git push origin +HEAD:main
|
|
52
|
+
(
|
|
53
|
+
re.compile(r"\bgit\s+push\b.*\s\+"),
|
|
54
|
+
"+refspec",
|
|
55
|
+
"force push via +refspec prefix (rewrites remote history)",
|
|
56
|
+
),
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _is_git_push_a_command(command: str) -> bool:
|
|
61
|
+
"""Check that 'git push' is an actual command, not a string argument.
|
|
62
|
+
|
|
63
|
+
Handles compound commands like "cd foo && git push --force" while
|
|
64
|
+
avoiding false positives like "echo 'git push --force'".
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
command: The shell command string to inspect.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
True if 'git push' appears as an actual command invocation.
|
|
71
|
+
"""
|
|
72
|
+
return bool(_SHELL_OPERATOR_RE.search(command))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def detect_force_push(command: str) -> ForcePushMatch | None:
|
|
76
|
+
"""Check if a shell command contains a git force push.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
command: The shell command string to inspect.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
ForcePushMatch if a force push pattern is found, None otherwise.
|
|
83
|
+
"""
|
|
84
|
+
# Quick pre-filter: skip entirely if "push" isn't even in the command
|
|
85
|
+
if "push" not in command:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
# Ensure 'git push' is an actual command, not a string argument
|
|
89
|
+
if not _is_git_push_a_command(command):
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
for pattern, name, description in _FORCE_PUSH_PATTERNS:
|
|
93
|
+
if pattern.search(command):
|
|
94
|
+
return ForcePushMatch(pattern_name=name, description=description)
|
|
95
|
+
|
|
96
|
+
return None
|