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,556 @@
|
|
|
1
|
+
"""Declarative TOML-based pipeline engine for shell output minimisation.
|
|
2
|
+
|
|
3
|
+
Models a ``CompiledPipeline`` that chains together text-transform
|
|
4
|
+
primitives from :mod:`code_muse.plugins.shell_minimizer.primitives`,
|
|
5
|
+
gated by exit code, and driven by TOML configuration.
|
|
6
|
+
|
|
7
|
+
Stages are applied in this order (each is optional)::
|
|
8
|
+
|
|
9
|
+
1. ``strip_ansi`` (bool)
|
|
10
|
+
2. ``replace`` (list of {pattern, replacement} regex subs)
|
|
11
|
+
3. ``match_output`` (list of {pattern, message, unless?})
|
|
12
|
+
4. ``strip_lines_matching`` / ``keep_lines_matching`` (mutually exclusive)
|
|
13
|
+
5. ``truncate_lines_at`` (int)
|
|
14
|
+
6. ``head_lines`` / ``tail_lines`` (int)
|
|
15
|
+
7. ``max_lines`` (int)
|
|
16
|
+
8. ``on_empty`` (string sentinel)
|
|
17
|
+
|
|
18
|
+
Exit-code gating is declared per pipeline via ``only_on_exit`` or
|
|
19
|
+
``except_on_exit``.
|
|
20
|
+
|
|
21
|
+
The module can be exercised standalone with ``python -m …`` for inline
|
|
22
|
+
tests (doctest-style).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import re
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# Import primitives lazily so the module is importable without them.
|
|
30
|
+
# They are required at *compile* time and *apply* time, though.
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
_primitives = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _get_primitives():
|
|
37
|
+
"""Lazy-load the primitives module once needed."""
|
|
38
|
+
global _primitives
|
|
39
|
+
if _primitives is None:
|
|
40
|
+
from code_muse.plugins.shell_minimizer import primitives as _p
|
|
41
|
+
|
|
42
|
+
_primitives = _p
|
|
43
|
+
return _primitives
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
# Models
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class ReplaceStep:
|
|
53
|
+
"""A regex-substitution step defined in TOML ``[[filters.xxx.replace]]``."""
|
|
54
|
+
|
|
55
|
+
pattern: str
|
|
56
|
+
replacement: str
|
|
57
|
+
_compiled: re.Pattern | None = field(default=None, repr=False)
|
|
58
|
+
|
|
59
|
+
def compile(self) -> None:
|
|
60
|
+
"""Pre-compile the pattern (called by the pipeline compiler).
|
|
61
|
+
|
|
62
|
+
Uses ``re.MULTILINE`` so that ``^`` / ``$`` anchors match
|
|
63
|
+
line boundaries, not just the whole-string boundaries.
|
|
64
|
+
"""
|
|
65
|
+
self._compiled = re.compile(self.pattern, re.MULTILINE)
|
|
66
|
+
|
|
67
|
+
def apply(self, text: str) -> str:
|
|
68
|
+
"""Run this substitution across *text*."""
|
|
69
|
+
if self._compiled is None:
|
|
70
|
+
self.compile()
|
|
71
|
+
return self._compiled.sub(self.replacement, text)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class MatchOutputRule:
|
|
76
|
+
"""Short-circuit rule: if the entire output matches *pattern*,
|
|
77
|
+
replace it with *message*. Optional *unless* pattern inverts
|
|
78
|
+
the match (only fires when *unless* does NOT match).
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
pattern: str
|
|
82
|
+
message: str
|
|
83
|
+
unless: str | None = None
|
|
84
|
+
_compiled_pattern: re.Pattern | None = field(default=None, repr=False)
|
|
85
|
+
_compiled_unless: re.Pattern | None = field(default=None, repr=False)
|
|
86
|
+
|
|
87
|
+
def compile(self) -> None:
|
|
88
|
+
self._compiled_pattern = re.compile(self.pattern, re.DOTALL)
|
|
89
|
+
if self.unless:
|
|
90
|
+
self._compiled_unless = re.compile(self.unless, re.DOTALL)
|
|
91
|
+
|
|
92
|
+
def matches(self, text: str) -> bool:
|
|
93
|
+
"""Return True if this rule should fire."""
|
|
94
|
+
if self._compiled_pattern is None:
|
|
95
|
+
self.compile()
|
|
96
|
+
if not self._compiled_pattern.fullmatch(text):
|
|
97
|
+
return False
|
|
98
|
+
return not (self._compiled_unless and self._compiled_unless.fullmatch(text))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass
|
|
102
|
+
class PipelineDef:
|
|
103
|
+
"""Raw pipeline definition as parsed from TOML (before compilation).
|
|
104
|
+
|
|
105
|
+
This is the intermediate representation that TOML tables map onto.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
name: str = ""
|
|
109
|
+
match_command: str | None = None
|
|
110
|
+
match_subcommand: str | None = None
|
|
111
|
+
|
|
112
|
+
# Stage flags
|
|
113
|
+
strip_ansi: bool = False
|
|
114
|
+
|
|
115
|
+
# Replace steps
|
|
116
|
+
replace: list[dict] = field(default_factory=list) # [{pattern, replacement}, ...]
|
|
117
|
+
|
|
118
|
+
# Match-output rules
|
|
119
|
+
match_output: list[dict] = field(default_factory=list)
|
|
120
|
+
|
|
121
|
+
# Line-filtering (mutually exclusive)
|
|
122
|
+
strip_lines_matching: list[str] = field(default_factory=list)
|
|
123
|
+
keep_lines_matching: list[str] = field(default_factory=list)
|
|
124
|
+
|
|
125
|
+
# Truncation
|
|
126
|
+
truncate_lines_at: int | None = None
|
|
127
|
+
|
|
128
|
+
# Head / tail / max (only one should be set typically)
|
|
129
|
+
head_lines: int | None = None
|
|
130
|
+
tail_lines: int | None = None
|
|
131
|
+
max_lines: int | None = None
|
|
132
|
+
|
|
133
|
+
# Empty-output sentinel
|
|
134
|
+
on_empty: str | None = None
|
|
135
|
+
|
|
136
|
+
# Exit-code gating
|
|
137
|
+
only_on_exit: list[int] | None = None
|
|
138
|
+
except_on_exit: list[int] | None = None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass
|
|
142
|
+
class CompiledPipeline:
|
|
143
|
+
"""A pipeline that has been compiled and is ready to apply.
|
|
144
|
+
|
|
145
|
+
All regex patterns are compiled, defaults are resolved, and the
|
|
146
|
+
stage list is flattened into an ordered list of callables.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
name: str = ""
|
|
150
|
+
match_command: re.Pattern | None = None
|
|
151
|
+
match_subcommand: re.Pattern | None = None
|
|
152
|
+
only_on_exit: list[int] | None = None
|
|
153
|
+
except_on_exit: list[int] | None = None
|
|
154
|
+
on_empty: str | None = None
|
|
155
|
+
|
|
156
|
+
# Ordered pipeline stages, each is a callable (str, exit_code?) -> str
|
|
157
|
+
_stages: list = field(default_factory=list)
|
|
158
|
+
|
|
159
|
+
def matches_program(self, command: str) -> bool:
|
|
160
|
+
"""Check whether *command* should be processed by this pipeline.
|
|
161
|
+
|
|
162
|
+
If neither ``match_command`` nor ``match_subcommand`` is set,
|
|
163
|
+
the pipeline is never auto-matched (must be invoked manually).
|
|
164
|
+
"""
|
|
165
|
+
if self.match_command is None:
|
|
166
|
+
return True # Manual pipelines match everything
|
|
167
|
+
|
|
168
|
+
# Extract first two tokens: e.g. "git diff --cached" → ["git", "diff"]
|
|
169
|
+
tokens = command.strip().split()
|
|
170
|
+
prog = tokens[0] if tokens else ""
|
|
171
|
+
subcmd = tokens[1] if len(tokens) > 1 else ""
|
|
172
|
+
|
|
173
|
+
if not self.match_command.fullmatch(prog):
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
if self.match_subcommand is not None:
|
|
177
|
+
if not self.match_subcommand.fullmatch(subcmd):
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
return True
|
|
181
|
+
|
|
182
|
+
def gated_by_exit(self, exit_code: int) -> bool:
|
|
183
|
+
"""Return True when the pipeline should be applied for *exit_code*."""
|
|
184
|
+
if self.only_on_exit is not None:
|
|
185
|
+
return exit_code in self.only_on_exit
|
|
186
|
+
if self.except_on_exit is not None:
|
|
187
|
+
return exit_code not in self.except_on_exit
|
|
188
|
+
return True # No gating — always apply
|
|
189
|
+
|
|
190
|
+
def apply(self, input: str, exit_code: int = 0) -> str:
|
|
191
|
+
"""Run the compiled pipeline on *input*.
|
|
192
|
+
|
|
193
|
+
Exit-code gating is checked first; if the pipeline should not
|
|
194
|
+
run for this exit code the input is returned unchanged.
|
|
195
|
+
"""
|
|
196
|
+
if not self.gated_by_exit(exit_code):
|
|
197
|
+
return input
|
|
198
|
+
|
|
199
|
+
text = input
|
|
200
|
+
|
|
201
|
+
for stage in self._stages:
|
|
202
|
+
try:
|
|
203
|
+
text = stage(text)
|
|
204
|
+
except Exception:
|
|
205
|
+
# Never crash on bad input; return what we have so far
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
# Short-circuit: match_output rules (checked AFTER stages)
|
|
209
|
+
for rule in self._match_rules:
|
|
210
|
+
if rule.matches(text):
|
|
211
|
+
return rule.message
|
|
212
|
+
|
|
213
|
+
if not text.strip() and self.on_empty is not None:
|
|
214
|
+
return self.on_empty
|
|
215
|
+
|
|
216
|
+
return text
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ---------------------------------------------------------------------------
|
|
220
|
+
# Compiler
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def compile_pipeline(name: str, raw: dict) -> CompiledPipeline:
|
|
225
|
+
"""Compile a single pipeline definition dict into a ``CompiledPipeline``.
|
|
226
|
+
|
|
227
|
+
Unknown keys are silently ignored so that schema extensions don't
|
|
228
|
+
break existing definitions.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
name: The pipeline name (used in logs and ``/minimizer`` display).
|
|
232
|
+
raw: A dict of pipeline config keys (the TOML table for the filter).
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
A ready-to-use ``CompiledPipeline``.
|
|
236
|
+
|
|
237
|
+
Raises:
|
|
238
|
+
ValueError: When mutually-exclusive options are set together
|
|
239
|
+
(e.g. ``strip_lines_matching`` + ``keep_lines_matching``,
|
|
240
|
+
or ``head_lines`` + ``tail_lines``).
|
|
241
|
+
"""
|
|
242
|
+
p = _get_primitives()
|
|
243
|
+
|
|
244
|
+
# --- Validate mutually-exclusive options --------------------------------
|
|
245
|
+
|
|
246
|
+
if raw.get("strip_lines_matching") and raw.get("keep_lines_matching"):
|
|
247
|
+
raise ValueError(
|
|
248
|
+
f"Pipeline '{name}': strip_lines_matching and keep_lines_matching "
|
|
249
|
+
"are mutually exclusive"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# --- Compile match patterns ----------------------------------------------
|
|
253
|
+
|
|
254
|
+
match_cmd = None
|
|
255
|
+
if raw.get("match_command"):
|
|
256
|
+
match_cmd = re.compile(raw["match_command"], re.IGNORECASE)
|
|
257
|
+
|
|
258
|
+
match_sub = None
|
|
259
|
+
if raw.get("match_subcommand"):
|
|
260
|
+
match_sub = re.compile(raw["match_subcommand"], re.IGNORECASE)
|
|
261
|
+
|
|
262
|
+
# --- Compile match-output rules ------------------------------------------
|
|
263
|
+
|
|
264
|
+
match_rules: list[MatchOutputRule] = []
|
|
265
|
+
for entry in raw.get("match_output", []) or []:
|
|
266
|
+
rule = MatchOutputRule(
|
|
267
|
+
pattern=entry["pattern"],
|
|
268
|
+
message=entry["message"],
|
|
269
|
+
unless=entry.get("unless"),
|
|
270
|
+
)
|
|
271
|
+
rule.compile()
|
|
272
|
+
match_rules.append(rule)
|
|
273
|
+
|
|
274
|
+
# --- Build ordered stage list --------------------------------------------
|
|
275
|
+
|
|
276
|
+
stages: list = []
|
|
277
|
+
|
|
278
|
+
# 1. strip_ansi
|
|
279
|
+
if raw.get("strip_ansi"):
|
|
280
|
+
stages.append(p.strip_ansi)
|
|
281
|
+
|
|
282
|
+
# 2. replace
|
|
283
|
+
replace_steps: list[ReplaceStep] = []
|
|
284
|
+
for entry in raw.get("replace", []) or []:
|
|
285
|
+
step = ReplaceStep(pattern=entry["pattern"], replacement=entry["replacement"])
|
|
286
|
+
step.compile()
|
|
287
|
+
replace_steps.append(step)
|
|
288
|
+
|
|
289
|
+
if replace_steps:
|
|
290
|
+
|
|
291
|
+
def _apply_replaces(text: str, _steps=replace_steps) -> str:
|
|
292
|
+
for step in _steps:
|
|
293
|
+
text = step.apply(text)
|
|
294
|
+
return text
|
|
295
|
+
|
|
296
|
+
stages.append(_apply_replaces)
|
|
297
|
+
|
|
298
|
+
# 3. match_output rules are checked in CompiledPipeline.apply(), not here
|
|
299
|
+
|
|
300
|
+
# 4. strip_lines_matching / keep_lines_matching
|
|
301
|
+
if raw.get("strip_lines_matching"):
|
|
302
|
+
patterns = list(raw["strip_lines_matching"])
|
|
303
|
+
stages.append(lambda t, pts=patterns: p.strip_lines_regex(t, pts))
|
|
304
|
+
|
|
305
|
+
if raw.get("keep_lines_matching"):
|
|
306
|
+
patterns = list(raw["keep_lines_matching"])
|
|
307
|
+
stages.append(lambda t, pts=patterns: p.keep_lines_regex(t, pts))
|
|
308
|
+
|
|
309
|
+
# 5. truncate_lines_at
|
|
310
|
+
if raw.get("truncate_lines_at") is not None:
|
|
311
|
+
max_chars = int(raw["truncate_lines_at"])
|
|
312
|
+
|
|
313
|
+
def _truncate_lines(text: str, _max=max_chars) -> str:
|
|
314
|
+
lines = text.splitlines()
|
|
315
|
+
truncated = [p.truncate_line(line, _max) for line in lines]
|
|
316
|
+
return "\n".join(truncated)
|
|
317
|
+
|
|
318
|
+
stages.append(_truncate_lines)
|
|
319
|
+
|
|
320
|
+
# 6. head_lines / tail_lines (or both, which uses head_tail_lines)
|
|
321
|
+
if raw.get("head_lines") is not None and raw.get("tail_lines") is not None:
|
|
322
|
+
head_n = int(raw["head_lines"])
|
|
323
|
+
tail_n = int(raw["tail_lines"])
|
|
324
|
+
stages.append(lambda t, h=head_n, t_n=tail_n: p.head_tail_lines(t, h, t_n))
|
|
325
|
+
elif raw.get("head_lines") is not None:
|
|
326
|
+
n = int(raw["head_lines"])
|
|
327
|
+
stages.append(lambda t, _n=n: p.head_lines_only(t, _n))
|
|
328
|
+
elif raw.get("tail_lines") is not None:
|
|
329
|
+
n = int(raw["tail_lines"])
|
|
330
|
+
stages.append(lambda t, _n=n: p.tail_lines_only(t, _n))
|
|
331
|
+
|
|
332
|
+
# 7. max_lines
|
|
333
|
+
if raw.get("max_lines") is not None:
|
|
334
|
+
n = int(raw["max_lines"])
|
|
335
|
+
stages.append(lambda t, _n=n: p.max_lines(t, _n))
|
|
336
|
+
|
|
337
|
+
# --- Assemble ------------------------------------------------------------
|
|
338
|
+
|
|
339
|
+
compiled = CompiledPipeline(
|
|
340
|
+
name=name,
|
|
341
|
+
match_command=match_cmd,
|
|
342
|
+
match_subcommand=match_sub,
|
|
343
|
+
only_on_exit=raw.get("only_on_exit"),
|
|
344
|
+
except_on_exit=raw.get("except_on_exit"),
|
|
345
|
+
on_empty=raw.get("on_empty"),
|
|
346
|
+
)
|
|
347
|
+
compiled._stages = stages
|
|
348
|
+
compiled._match_rules = match_rules # type: ignore[assignment]
|
|
349
|
+
|
|
350
|
+
return compiled
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# ---------------------------------------------------------------------------
|
|
354
|
+
# TOML parser
|
|
355
|
+
# ---------------------------------------------------------------------------
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def parse_pipeline_toml(
|
|
359
|
+
contents: str, source_label: str = "<string>"
|
|
360
|
+
) -> list[CompiledPipeline]:
|
|
361
|
+
"""Parse TOML-format pipeline definitions.
|
|
362
|
+
|
|
363
|
+
Expects a TOML document with ``schema_version = 1`` and one or more
|
|
364
|
+
``[filters.<name>]`` tables. Returns a list of compiled pipelines
|
|
365
|
+
ready to apply.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
contents: Raw TOML text.
|
|
369
|
+
source_label: Describes the source for error messages.
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
List of ``CompiledPipeline`` instances (may be empty).
|
|
373
|
+
|
|
374
|
+
Raises:
|
|
375
|
+
ValueError: On parse errors, schema violations, or invalid pipeline
|
|
376
|
+
config (e.g. mutually-exclusive options).
|
|
377
|
+
"""
|
|
378
|
+
import tomllib as toml_module
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
data = toml_module.loads(contents)
|
|
382
|
+
except Exception as exc:
|
|
383
|
+
raise ValueError(f"TOML parse error in {source_label}: {exc}") from exc
|
|
384
|
+
|
|
385
|
+
schema = data.get("schema_version")
|
|
386
|
+
if schema != 1:
|
|
387
|
+
raise ValueError(f"{source_label}: expected schema_version=1, got {schema!r}")
|
|
388
|
+
|
|
389
|
+
filters = data.get("filters", {})
|
|
390
|
+
if not isinstance(filters, dict):
|
|
391
|
+
raise ValueError(f"{source_label}: [filters] must be a table")
|
|
392
|
+
|
|
393
|
+
compiled_pipelines: list[CompiledPipeline] = []
|
|
394
|
+
errors: list[str] = []
|
|
395
|
+
|
|
396
|
+
for name, raw in filters.items():
|
|
397
|
+
if not isinstance(raw, dict):
|
|
398
|
+
errors.append(f" {name}: value must be a table, got {type(raw).__name__}")
|
|
399
|
+
continue
|
|
400
|
+
try:
|
|
401
|
+
compiled = compile_pipeline(name, raw)
|
|
402
|
+
compiled_pipelines.append(compiled)
|
|
403
|
+
except ValueError as exc:
|
|
404
|
+
errors.append(f" {name}: {exc}")
|
|
405
|
+
|
|
406
|
+
if errors:
|
|
407
|
+
joined = "\n".join(errors)
|
|
408
|
+
raise ValueError(f"Pipeline compilation errors in {source_label}:\n{joined}")
|
|
409
|
+
|
|
410
|
+
return compiled_pipelines
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
# ---------------------------------------------------------------------------
|
|
414
|
+
# Top-level convenience
|
|
415
|
+
# ---------------------------------------------------------------------------
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def apply_pipeline(pipeline: CompiledPipeline, input: str, exit_code: int = 0) -> str:
|
|
419
|
+
"""Apply a compiled pipeline to *input* with optional exit-code gating.
|
|
420
|
+
|
|
421
|
+
Thin wrapper around ``CompiledPipeline.apply`` for external callers.
|
|
422
|
+
"""
|
|
423
|
+
return pipeline.apply(input, exit_code)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
# ---------------------------------------------------------------------------
|
|
427
|
+
# Inline tests (run with ``python -m code_muse.plugins.shell_minimizer.pipeline``)
|
|
428
|
+
# ---------------------------------------------------------------------------
|
|
429
|
+
|
|
430
|
+
if __name__ == "__main__":
|
|
431
|
+
# --- Test compile_pipeline basics ---------------------------------------
|
|
432
|
+
|
|
433
|
+
cp = compile_pipeline(
|
|
434
|
+
"test_basic",
|
|
435
|
+
{
|
|
436
|
+
"strip_ansi": True,
|
|
437
|
+
"truncate_lines_at": 80,
|
|
438
|
+
"head_lines": 5,
|
|
439
|
+
"on_empty": "(nothing)",
|
|
440
|
+
},
|
|
441
|
+
)
|
|
442
|
+
assert cp.name == "test_basic"
|
|
443
|
+
assert cp.on_empty == "(nothing)"
|
|
444
|
+
assert len(cp._stages) == 3 # strip_ansi, truncate, head_lines
|
|
445
|
+
|
|
446
|
+
# Apply to ANSI-laden text
|
|
447
|
+
ansi_input = "\x1b[32mgreen\x1b[0m\n" + "x" * 200
|
|
448
|
+
result = cp.apply(ansi_input)
|
|
449
|
+
assert "green" in result
|
|
450
|
+
assert "…" in result or "more" in result.lower()
|
|
451
|
+
print("✅ compile/apply basic")
|
|
452
|
+
|
|
453
|
+
# --- Test exit-code gating -----------------------------------------------
|
|
454
|
+
|
|
455
|
+
gated = compile_pipeline(
|
|
456
|
+
"gated_test",
|
|
457
|
+
{
|
|
458
|
+
"strip_ansi": True,
|
|
459
|
+
"only_on_exit": [0],
|
|
460
|
+
"on_empty": "(clean exit)",
|
|
461
|
+
},
|
|
462
|
+
)
|
|
463
|
+
assert gated.apply("some output", exit_code=0) == "some output"
|
|
464
|
+
assert gated.apply("some output", exit_code=1) == "some output" # unchanged
|
|
465
|
+
print("✅ exit-code gating")
|
|
466
|
+
|
|
467
|
+
# --- Test match_output short-circuit -------------------------------------
|
|
468
|
+
|
|
469
|
+
short = compile_pipeline(
|
|
470
|
+
"empty_match",
|
|
471
|
+
{
|
|
472
|
+
"match_output": [
|
|
473
|
+
{"pattern": r"^\s*$", "message": "(empty output)"},
|
|
474
|
+
],
|
|
475
|
+
},
|
|
476
|
+
)
|
|
477
|
+
assert short.apply("\n \n") == "(empty output)"
|
|
478
|
+
assert short.apply("real data") == "real data"
|
|
479
|
+
print("✅ match_output short-circuit")
|
|
480
|
+
|
|
481
|
+
# --- Test mutual-exclusivity errors --------------------------------------
|
|
482
|
+
|
|
483
|
+
try:
|
|
484
|
+
compile_pipeline(
|
|
485
|
+
"bad",
|
|
486
|
+
{"strip_lines_matching": ["x"], "keep_lines_matching": ["y"]},
|
|
487
|
+
)
|
|
488
|
+
raise AssertionError("should have raised")
|
|
489
|
+
except ValueError as e:
|
|
490
|
+
assert "mutually exclusive" in str(e)
|
|
491
|
+
print("✅ mutual-exclusivity check")
|
|
492
|
+
|
|
493
|
+
# --- Test replace steps --------------------------------------------------
|
|
494
|
+
|
|
495
|
+
repl = compile_pipeline(
|
|
496
|
+
"replace_test",
|
|
497
|
+
{
|
|
498
|
+
"replace": [
|
|
499
|
+
{"pattern": r"error:", "replacement": "ERR:"},
|
|
500
|
+
{"pattern": r"\x1b\[[0-9;]*m", "replacement": ""},
|
|
501
|
+
],
|
|
502
|
+
},
|
|
503
|
+
)
|
|
504
|
+
assert repl.apply("error: something") == "ERR: something"
|
|
505
|
+
assert repl.apply("\x1b[31merror:\x1b[0m fail") == "ERR: fail"
|
|
506
|
+
print("✅ replace steps")
|
|
507
|
+
|
|
508
|
+
# --- Test TOML parsing (inline) ------------------------------------------
|
|
509
|
+
|
|
510
|
+
toml_text = """\
|
|
511
|
+
schema_version = 1
|
|
512
|
+
|
|
513
|
+
[filters.git_diff]
|
|
514
|
+
match_command = "^git$"
|
|
515
|
+
match_subcommand = "^diff$"
|
|
516
|
+
strip_ansi = true
|
|
517
|
+
truncate_lines_at = 160
|
|
518
|
+
head_lines = 40
|
|
519
|
+
tail_lines = 20
|
|
520
|
+
|
|
521
|
+
[[filters.git_diff.match_output]]
|
|
522
|
+
pattern = "^$"
|
|
523
|
+
message = "(empty diff)"
|
|
524
|
+
|
|
525
|
+
[filters.cargo_build]
|
|
526
|
+
match_command = "^cargo$"
|
|
527
|
+
match_subcommand = "^(build|test|clippy)$"
|
|
528
|
+
strip_ansi = true
|
|
529
|
+
# Keep only error/warning/note lines
|
|
530
|
+
keep_lines_matching = ["^error", "^warning", "^note", "^Compiling", "^Finished", "^Running"]
|
|
531
|
+
truncate_lines_at = 120
|
|
532
|
+
head_lines = 30
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
pipelines = parse_pipeline_toml(toml_text, "<inline test>")
|
|
536
|
+
assert len(pipelines) == 2, f"expected 2, got {len(pipelines)}"
|
|
537
|
+
|
|
538
|
+
git_diff = pipelines[0]
|
|
539
|
+
assert git_diff.name == "git_diff"
|
|
540
|
+
assert git_diff.match_command is not None
|
|
541
|
+
assert git_diff.match_subcommand is not None
|
|
542
|
+
# Check it matches "git diff"
|
|
543
|
+
assert git_diff.matches_program("git diff --cached")
|
|
544
|
+
assert not git_diff.matches_program("git push")
|
|
545
|
+
print("✅ TOML parsing & program matching")
|
|
546
|
+
|
|
547
|
+
# --- Test program matching more thoroughly -------------------------------
|
|
548
|
+
|
|
549
|
+
cargo = pipelines[1]
|
|
550
|
+
assert cargo.matches_program("cargo build")
|
|
551
|
+
assert cargo.matches_program("cargo test")
|
|
552
|
+
assert cargo.matches_program("cargo clippy")
|
|
553
|
+
assert not cargo.matches_program("cargo run")
|
|
554
|
+
print("✅ cargo subcommand matching")
|
|
555
|
+
|
|
556
|
+
print("\n🎉 All pipeline tests passed!")
|