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,54 @@
|
|
|
1
|
+
"""Register token-caching callbacks and the /cache slash command."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from code_muse.callbacks import register_callback
|
|
6
|
+
from code_muse.messaging import emit_info
|
|
7
|
+
from code_muse.plugins.token_caching.cache_hit_tracking import _session_stats
|
|
8
|
+
from code_muse.plugins.token_caching.stats_display import format_cache_stats
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# Slash-command help
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _on_custom_command_help() -> list[tuple[str, str]]:
|
|
19
|
+
"""Provide help entry for /help display."""
|
|
20
|
+
return [("cache", "Show token caching statistics")]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Slash-command handler
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def _on_custom_command(command: str, name: str) -> bool | None:
|
|
29
|
+
"""Handle the /cache slash command.
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
/cache
|
|
33
|
+
|
|
34
|
+
Displays current session cache statistics.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
``True`` if handled, ``None`` if the command name doesn't match.
|
|
38
|
+
"""
|
|
39
|
+
if name != "cache":
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
stats_text = format_cache_stats(_session_stats)
|
|
43
|
+
emit_info(stats_text)
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# Register callbacks
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
register_callback("custom_command_help", _on_custom_command_help)
|
|
52
|
+
register_callback("custom_command", _on_custom_command)
|
|
53
|
+
|
|
54
|
+
logger.debug("Token Caching plugin callbacks registered")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Format cache stats for human and LLM display."""
|
|
2
|
+
|
|
3
|
+
from code_muse.plugins.token_caching.cache_hit_tracking import SessionCacheStats
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def format_cache_stats(stats: SessionCacheStats) -> str:
|
|
7
|
+
"""Return a compact, human-readable summary of cache stats.
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
``"Cache: 42 hits (12,500 tokens read) · 3 writes (1,200 tokens written) · hit rate 78.3% · est. savings $0.12"``
|
|
11
|
+
|
|
12
|
+
If there has been no cache activity this session, returns:
|
|
13
|
+
``"Cache: no activity this session"``
|
|
14
|
+
"""
|
|
15
|
+
reads = stats.total_read_tokens
|
|
16
|
+
writes = stats.total_write_tokens
|
|
17
|
+
hits = stats.hit_rate
|
|
18
|
+
|
|
19
|
+
if reads == 0 and writes == 0:
|
|
20
|
+
return "Cache: no activity this session"
|
|
21
|
+
|
|
22
|
+
savings = stats.estimated_savings_usd
|
|
23
|
+
|
|
24
|
+
# Format numbers with commas
|
|
25
|
+
reads_str = f"{reads:,}"
|
|
26
|
+
writes_str = f"{writes:,}"
|
|
27
|
+
hit_rate_str = f"{hits * 100:.1f}%"
|
|
28
|
+
savings_str = f"${savings:.2f}"
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
f"Cache: {reads_str} tokens read"
|
|
32
|
+
f" · {writes_str} tokens written"
|
|
33
|
+
f" · hit rate {hit_rate_str}"
|
|
34
|
+
f" · est. savings {savings_str}"
|
|
35
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Token tracking plugin for Muse.
|
|
2
|
+
|
|
3
|
+
Records every command execution with token counts and provides slash-command
|
|
4
|
+
reports for gain, economics, session adoption, and edit efficiency.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from code_muse.plugins.token_tracking.database import TrackingDatabase, get_tracking_db
|
|
8
|
+
from code_muse.plugins.token_tracking.edit_analyzer import analyze_replacement
|
|
9
|
+
from code_muse.plugins.token_tracking.record import record_command
|
|
10
|
+
from code_muse.plugins.token_tracking.reports import (
|
|
11
|
+
cc_economics_report,
|
|
12
|
+
edit_efficiency_report,
|
|
13
|
+
gain_report,
|
|
14
|
+
session_report,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"TrackingDatabase",
|
|
19
|
+
"analyze_replacement",
|
|
20
|
+
"cc_economics_report",
|
|
21
|
+
"edit_efficiency_report",
|
|
22
|
+
"gain_report",
|
|
23
|
+
"get_tracking_db",
|
|
24
|
+
"record_command",
|
|
25
|
+
"session_report",
|
|
26
|
+
]
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""SQLite tracking database for token usage history.
|
|
2
|
+
|
|
3
|
+
Stores every command execution with raw vs compressed token counts,
|
|
4
|
+
enabling gain reports and economics analysis.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import contextlib
|
|
8
|
+
import logging
|
|
9
|
+
import sqlite3
|
|
10
|
+
import threading
|
|
11
|
+
from datetime import UTC, datetime
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
SCHEMA_V1 = """
|
|
18
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
19
|
+
version INTEGER PRIMARY KEY
|
|
20
|
+
);
|
|
21
|
+
INSERT OR IGNORE INTO schema_version VALUES (1);
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS executions (
|
|
24
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
25
|
+
command TEXT NOT NULL,
|
|
26
|
+
category TEXT NOT NULL,
|
|
27
|
+
strategy TEXT NOT NULL,
|
|
28
|
+
raw_tokens INTEGER NOT NULL,
|
|
29
|
+
compressed_tokens INTEGER NOT NULL,
|
|
30
|
+
savings_pct REAL NOT NULL,
|
|
31
|
+
timestamp TEXT NOT NULL,
|
|
32
|
+
session_id TEXT NOT NULL,
|
|
33
|
+
exit_code INTEGER,
|
|
34
|
+
duration_ms REAL
|
|
35
|
+
);
|
|
36
|
+
CREATE INDEX IF NOT EXISTS idx_executions_timestamp ON executions(timestamp);
|
|
37
|
+
CREATE INDEX IF NOT EXISTS idx_executions_session ON executions(session_id);
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
SCHEMA_MIGRATION_V2 = """
|
|
41
|
+
ALTER TABLE schema_version ADD COLUMN migration_v2 INTEGER DEFAULT 0;
|
|
42
|
+
INSERT OR IGNORE INTO schema_version (version, migration_v2) VALUES (2, 1);
|
|
43
|
+
|
|
44
|
+
CREATE TABLE IF NOT EXISTS edit_analysis (
|
|
45
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
46
|
+
tool_name TEXT NOT NULL,
|
|
47
|
+
file_path TEXT NOT NULL,
|
|
48
|
+
old_bytes INTEGER,
|
|
49
|
+
new_bytes INTEGER,
|
|
50
|
+
total_edit_bytes INTEGER,
|
|
51
|
+
shared_prefix_bytes INTEGER,
|
|
52
|
+
shared_suffix_bytes INTEGER,
|
|
53
|
+
shared_context_bytes INTEGER,
|
|
54
|
+
core_old_bytes INTEGER,
|
|
55
|
+
core_new_bytes INTEGER,
|
|
56
|
+
core_bytes INTEGER,
|
|
57
|
+
wrapper_payload_bytes INTEGER,
|
|
58
|
+
inflation_ratio REAL,
|
|
59
|
+
no_core_change INTEGER DEFAULT 0,
|
|
60
|
+
success INTEGER DEFAULT 1,
|
|
61
|
+
timestamp TEXT NOT NULL,
|
|
62
|
+
session_id TEXT NOT NULL
|
|
63
|
+
);
|
|
64
|
+
CREATE INDEX IF NOT EXISTS idx_edit_analysis_timestamp ON edit_analysis(timestamp);
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_edit_analysis_session ON edit_analysis(session_id);
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class TrackingDatabase:
|
|
70
|
+
"""Thread-safe SQLite database for tracking command executions.
|
|
71
|
+
|
|
72
|
+
Uses WAL mode for concurrency and a single shared connection
|
|
73
|
+
protected by a threading lock.
|
|
74
|
+
|
|
75
|
+
This is a plain class. For the shared application instance use
|
|
76
|
+
:func:`get_tracking_db`.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
DB_PATH: Path = Path.home() / ".muse" / "tracking.db"
|
|
80
|
+
_CLEANUP_EVERY_N = 100
|
|
81
|
+
|
|
82
|
+
def __init__(self, db_path: Path | str | None = None) -> None:
|
|
83
|
+
"""Initialise (or connect to) the tracking database.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
db_path: Override the default ``~/.muse/tracking.db``.
|
|
87
|
+
"""
|
|
88
|
+
self._db_path = Path(db_path) if db_path is not None else self.DB_PATH
|
|
89
|
+
self._connection: sqlite3.Connection | None = None
|
|
90
|
+
self._lock = threading.Lock()
|
|
91
|
+
self._insert_count = 0
|
|
92
|
+
self._ensure_dir()
|
|
93
|
+
self._run_migrations()
|
|
94
|
+
self.cleanup()
|
|
95
|
+
|
|
96
|
+
# ------------------------------------------------------------------
|
|
97
|
+
# Connection lifecycle
|
|
98
|
+
# ------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
def _ensure_dir(self) -> None:
|
|
101
|
+
"""Create the parent directory if it doesn't exist."""
|
|
102
|
+
self._db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
|
|
104
|
+
def get_connection(self) -> sqlite3.Connection:
|
|
105
|
+
"""Lazy-initialise and return the shared connection."""
|
|
106
|
+
if self._connection is None:
|
|
107
|
+
with self._lock:
|
|
108
|
+
if self._connection is None:
|
|
109
|
+
self._connection = sqlite3.connect(
|
|
110
|
+
str(self._db_path),
|
|
111
|
+
check_same_thread=False,
|
|
112
|
+
)
|
|
113
|
+
self._connection.execute("PRAGMA journal_mode=WAL")
|
|
114
|
+
self._connection.execute("PRAGMA foreign_keys=ON")
|
|
115
|
+
return self._connection
|
|
116
|
+
|
|
117
|
+
def close(self) -> None:
|
|
118
|
+
"""Close the shared connection (idempotent)."""
|
|
119
|
+
with self._lock:
|
|
120
|
+
if self._connection is not None:
|
|
121
|
+
with contextlib.suppress(Exception):
|
|
122
|
+
self._connection.close()
|
|
123
|
+
self._connection = None
|
|
124
|
+
|
|
125
|
+
# ------------------------------------------------------------------
|
|
126
|
+
# Migrations
|
|
127
|
+
# ------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
def _run_migrations(self) -> None:
|
|
130
|
+
"""Ensure schema is at the latest version."""
|
|
131
|
+
conn = self.get_connection()
|
|
132
|
+
with self._lock:
|
|
133
|
+
# Check if schema_version table exists (v1 baseline)
|
|
134
|
+
try:
|
|
135
|
+
conn.execute("SELECT version FROM schema_version LIMIT 1").fetchone()
|
|
136
|
+
except sqlite3.OperationalError:
|
|
137
|
+
# First install — run full schema
|
|
138
|
+
conn.executescript(SCHEMA_V1)
|
|
139
|
+
conn.commit()
|
|
140
|
+
|
|
141
|
+
# Check v2 migration
|
|
142
|
+
try:
|
|
143
|
+
conn.execute(
|
|
144
|
+
"SELECT migration_v2 FROM schema_version LIMIT 1"
|
|
145
|
+
).fetchone()
|
|
146
|
+
except sqlite3.OperationalError:
|
|
147
|
+
conn.executescript(SCHEMA_MIGRATION_V2)
|
|
148
|
+
conn.commit()
|
|
149
|
+
|
|
150
|
+
# ------------------------------------------------------------------
|
|
151
|
+
# Public API
|
|
152
|
+
# ------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
def insert(
|
|
155
|
+
self,
|
|
156
|
+
command: str,
|
|
157
|
+
category: str,
|
|
158
|
+
strategy: str,
|
|
159
|
+
raw_tokens: int,
|
|
160
|
+
compressed_tokens: int,
|
|
161
|
+
savings_pct: float,
|
|
162
|
+
session_id: str,
|
|
163
|
+
exit_code: int = 0,
|
|
164
|
+
duration_ms: float = 0.0,
|
|
165
|
+
) -> int:
|
|
166
|
+
"""Record a command execution.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
The new row id, or ``-1`` on error (never raises).
|
|
170
|
+
"""
|
|
171
|
+
try:
|
|
172
|
+
timestamp = datetime.now(UTC).isoformat()
|
|
173
|
+
conn = self.get_connection()
|
|
174
|
+
with self._lock:
|
|
175
|
+
cursor = conn.execute(
|
|
176
|
+
"""
|
|
177
|
+
INSERT INTO executions
|
|
178
|
+
(command, category, strategy, raw_tokens, compressed_tokens,
|
|
179
|
+
savings_pct, timestamp, session_id, exit_code, duration_ms)
|
|
180
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
181
|
+
""",
|
|
182
|
+
(
|
|
183
|
+
command,
|
|
184
|
+
category,
|
|
185
|
+
strategy,
|
|
186
|
+
raw_tokens,
|
|
187
|
+
compressed_tokens,
|
|
188
|
+
savings_pct,
|
|
189
|
+
timestamp,
|
|
190
|
+
session_id,
|
|
191
|
+
exit_code,
|
|
192
|
+
duration_ms,
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
conn.commit()
|
|
196
|
+
row_id = cursor.lastrowid or -1
|
|
197
|
+
|
|
198
|
+
self._insert_count += 1
|
|
199
|
+
if self._insert_count % self._CLEANUP_EVERY_N == 0:
|
|
200
|
+
self.cleanup()
|
|
201
|
+
return row_id
|
|
202
|
+
except Exception as exc:
|
|
203
|
+
logger.warning("TrackingDatabase insert failed: %s", exc)
|
|
204
|
+
return -1
|
|
205
|
+
|
|
206
|
+
def insert_edit_analysis(
|
|
207
|
+
self,
|
|
208
|
+
tool_name: str,
|
|
209
|
+
file_path: str,
|
|
210
|
+
old_bytes: int,
|
|
211
|
+
new_bytes: int,
|
|
212
|
+
total_edit_bytes: int,
|
|
213
|
+
shared_prefix_bytes: int,
|
|
214
|
+
shared_suffix_bytes: int,
|
|
215
|
+
shared_context_bytes: int,
|
|
216
|
+
core_old_bytes: int,
|
|
217
|
+
core_new_bytes: int,
|
|
218
|
+
core_bytes: int,
|
|
219
|
+
wrapper_payload_bytes: int,
|
|
220
|
+
inflation_ratio: float | None,
|
|
221
|
+
no_core_change: bool,
|
|
222
|
+
success: bool = True,
|
|
223
|
+
session_id: str = "",
|
|
224
|
+
) -> int:
|
|
225
|
+
"""Record an edit operation analysis. Returns row id or -1."""
|
|
226
|
+
try:
|
|
227
|
+
timestamp = datetime.now(UTC).isoformat()
|
|
228
|
+
conn = self.get_connection()
|
|
229
|
+
with self._lock:
|
|
230
|
+
cursor = conn.execute(
|
|
231
|
+
"""
|
|
232
|
+
INSERT INTO edit_analysis
|
|
233
|
+
(tool_name, file_path, old_bytes, new_bytes, total_edit_bytes,
|
|
234
|
+
shared_prefix_bytes, shared_suffix_bytes, shared_context_bytes,
|
|
235
|
+
core_old_bytes, core_new_bytes, core_bytes, wrapper_payload_bytes,
|
|
236
|
+
inflation_ratio, no_core_change, success, timestamp, session_id)
|
|
237
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
238
|
+
""",
|
|
239
|
+
(
|
|
240
|
+
tool_name,
|
|
241
|
+
file_path,
|
|
242
|
+
old_bytes,
|
|
243
|
+
new_bytes,
|
|
244
|
+
total_edit_bytes,
|
|
245
|
+
shared_prefix_bytes,
|
|
246
|
+
shared_suffix_bytes,
|
|
247
|
+
shared_context_bytes,
|
|
248
|
+
core_old_bytes,
|
|
249
|
+
core_new_bytes,
|
|
250
|
+
core_bytes,
|
|
251
|
+
wrapper_payload_bytes,
|
|
252
|
+
inflation_ratio,
|
|
253
|
+
1 if no_core_change else 0,
|
|
254
|
+
1 if success else 0,
|
|
255
|
+
timestamp,
|
|
256
|
+
session_id,
|
|
257
|
+
),
|
|
258
|
+
)
|
|
259
|
+
conn.commit()
|
|
260
|
+
return cursor.lastrowid or -1
|
|
261
|
+
except Exception as exc:
|
|
262
|
+
logger.warning("TrackingDatabase insert_edit_analysis failed: %s", exc)
|
|
263
|
+
return -1
|
|
264
|
+
|
|
265
|
+
def query_edit_summary(self, time_range: str = "all") -> list[dict]:
|
|
266
|
+
"""Get edit efficiency summary for the given time range.
|
|
267
|
+
|
|
268
|
+
Returns a list of dicts, one per edit_analysis row.
|
|
269
|
+
"""
|
|
270
|
+
where = {
|
|
271
|
+
"today": "date(timestamp) = date('now')",
|
|
272
|
+
"week": "timestamp >= datetime('now', '-7 days')",
|
|
273
|
+
"month": "timestamp >= datetime('now', '-30 days')",
|
|
274
|
+
"all": "1=1",
|
|
275
|
+
}.get(time_range, "1=1")
|
|
276
|
+
|
|
277
|
+
conn = self.get_connection()
|
|
278
|
+
with self._lock:
|
|
279
|
+
rows = conn.execute(
|
|
280
|
+
# TODO: PEP 750 t-string — use templatelib when stable
|
|
281
|
+
f"""
|
|
282
|
+
SELECT
|
|
283
|
+
id, tool_name, file_path, old_bytes, new_bytes,
|
|
284
|
+
total_edit_bytes, shared_prefix_bytes, shared_suffix_bytes,
|
|
285
|
+
shared_context_bytes, core_old_bytes, core_new_bytes,
|
|
286
|
+
core_bytes, wrapper_payload_bytes, inflation_ratio,
|
|
287
|
+
no_core_change, success, timestamp, session_id
|
|
288
|
+
FROM edit_analysis
|
|
289
|
+
WHERE {where}
|
|
290
|
+
ORDER BY timestamp DESC
|
|
291
|
+
""",
|
|
292
|
+
).fetchall()
|
|
293
|
+
|
|
294
|
+
columns = [
|
|
295
|
+
"id",
|
|
296
|
+
"tool_name",
|
|
297
|
+
"file_path",
|
|
298
|
+
"old_bytes",
|
|
299
|
+
"new_bytes",
|
|
300
|
+
"total_edit_bytes",
|
|
301
|
+
"shared_prefix_bytes",
|
|
302
|
+
"shared_suffix_bytes",
|
|
303
|
+
"shared_context_bytes",
|
|
304
|
+
"core_old_bytes",
|
|
305
|
+
"core_new_bytes",
|
|
306
|
+
"core_bytes",
|
|
307
|
+
"wrapper_payload_bytes",
|
|
308
|
+
"inflation_ratio",
|
|
309
|
+
"no_core_change",
|
|
310
|
+
"success",
|
|
311
|
+
"timestamp",
|
|
312
|
+
"session_id",
|
|
313
|
+
]
|
|
314
|
+
return [dict(zip(columns, row, strict=False)) for row in rows]
|
|
315
|
+
|
|
316
|
+
def cleanup(self, retention_days: int = 90) -> int:
|
|
317
|
+
"""Delete records older than *retention_days*.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
Number of rows deleted.
|
|
321
|
+
"""
|
|
322
|
+
try:
|
|
323
|
+
conn = self.get_connection()
|
|
324
|
+
with self._lock:
|
|
325
|
+
cursor = conn.execute(
|
|
326
|
+
"""
|
|
327
|
+
DELETE FROM executions
|
|
328
|
+
WHERE timestamp < datetime('now', ?)
|
|
329
|
+
""",
|
|
330
|
+
(f"-{retention_days} days",),
|
|
331
|
+
)
|
|
332
|
+
cursor2 = conn.execute(
|
|
333
|
+
"""
|
|
334
|
+
DELETE FROM edit_analysis
|
|
335
|
+
WHERE timestamp < datetime('now', ?)
|
|
336
|
+
""",
|
|
337
|
+
(f"-{retention_days} days",),
|
|
338
|
+
)
|
|
339
|
+
conn.commit()
|
|
340
|
+
return cursor.rowcount + cursor2.rowcount
|
|
341
|
+
except Exception as exc:
|
|
342
|
+
logger.warning("TrackingDatabase cleanup failed: %s", exc)
|
|
343
|
+
return 0
|
|
344
|
+
|
|
345
|
+
# ------------------------------------------------------------------
|
|
346
|
+
# Query helpers
|
|
347
|
+
# ------------------------------------------------------------------
|
|
348
|
+
|
|
349
|
+
def query_one(self, sql: str, parameters: tuple[Any, ...] = ()) -> Any:
|
|
350
|
+
"""Execute a query and return the first row (or None)."""
|
|
351
|
+
conn = self.get_connection()
|
|
352
|
+
with self._lock:
|
|
353
|
+
return conn.execute(sql, parameters).fetchone()
|
|
354
|
+
|
|
355
|
+
def query_all(self, sql: str, parameters: tuple[Any, ...] = ()) -> list[Any]:
|
|
356
|
+
"""Execute a query and return all rows."""
|
|
357
|
+
conn = self.get_connection()
|
|
358
|
+
with self._lock:
|
|
359
|
+
return conn.execute(sql, parameters).fetchall()
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# Module-level singleton cache — simple, testable, explicit.
|
|
363
|
+
_tracking_db_instance: TrackingDatabase | None = None
|
|
364
|
+
_tracking_db_lock = threading.Lock()
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def get_tracking_db(db_path: Path | str | None = None) -> TrackingDatabase:
|
|
368
|
+
"""Return the shared :class:`TrackingDatabase` instance.
|
|
369
|
+
|
|
370
|
+
The first call creates and caches the instance; subsequent calls
|
|
371
|
+
return the same object. Thread-safe.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
db_path: Override the default ``~/.muse/tracking.db``.
|
|
375
|
+
"""
|
|
376
|
+
global _tracking_db_instance
|
|
377
|
+
if _tracking_db_instance is None:
|
|
378
|
+
with _tracking_db_lock:
|
|
379
|
+
if _tracking_db_instance is None:
|
|
380
|
+
_tracking_db_instance = TrackingDatabase(db_path)
|
|
381
|
+
return _tracking_db_instance
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Edit context inflation analyzer — ported from Pi project.
|
|
2
|
+
|
|
3
|
+
Measures how much wrapper/boilerplate code surrounds the actual change
|
|
4
|
+
in file edit operations, revealing token waste.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _utf8_bytes(text: str) -> int:
|
|
9
|
+
"""Count UTF-8 bytes in a string."""
|
|
10
|
+
return len(text.encode("utf-8"))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _longest_common_prefix_len(a: str, b: str) -> int:
|
|
14
|
+
"""Length of the longest common prefix between two strings."""
|
|
15
|
+
max_len = min(len(a), len(b))
|
|
16
|
+
i = 0
|
|
17
|
+
while i < max_len and a[i] == b[i]:
|
|
18
|
+
i += 1
|
|
19
|
+
return i
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _longest_common_suffix_len(a: str, b: str) -> int:
|
|
23
|
+
"""Length of the longest common suffix between two strings."""
|
|
24
|
+
max_len = min(len(a), len(b))
|
|
25
|
+
i = 0
|
|
26
|
+
while i < max_len and a[-(i + 1)] == b[-(i + 1)]:
|
|
27
|
+
i += 1
|
|
28
|
+
return i
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def analyze_replacement(
|
|
32
|
+
old_text: str,
|
|
33
|
+
new_text: str,
|
|
34
|
+
) -> dict:
|
|
35
|
+
"""Analyze an old→new text replacement for context inflation.
|
|
36
|
+
|
|
37
|
+
Returns a dict with byte-level metrics:
|
|
38
|
+
- old_bytes, new_bytes: raw UTF-8 byte counts
|
|
39
|
+
- total_edit_bytes: old + new
|
|
40
|
+
- shared_prefix_bytes, shared_suffix_bytes: identical wrapper context
|
|
41
|
+
- shared_context_bytes: prefix + suffix
|
|
42
|
+
- core_old_bytes, core_new_bytes: the actual changed portions
|
|
43
|
+
- core_bytes: core_old + core_new (the meaningful change)
|
|
44
|
+
- wrapper_payload_bytes: total - core (the overhead)
|
|
45
|
+
- inflation_ratio: total / core (None if no core change)
|
|
46
|
+
- no_core_change: True when old == new (pure wrapper churn)
|
|
47
|
+
"""
|
|
48
|
+
old_bytes = _utf8_bytes(old_text)
|
|
49
|
+
new_bytes = _utf8_bytes(new_text)
|
|
50
|
+
total = old_bytes + new_bytes
|
|
51
|
+
|
|
52
|
+
prefix_chars = _longest_common_prefix_len(old_text, new_text)
|
|
53
|
+
old_remainder = old_text[prefix_chars:]
|
|
54
|
+
new_remainder = new_text[prefix_chars:]
|
|
55
|
+
|
|
56
|
+
suffix_chars = _longest_common_suffix_len(old_remainder, new_remainder)
|
|
57
|
+
|
|
58
|
+
old_core = (
|
|
59
|
+
old_remainder[: len(old_remainder) - suffix_chars]
|
|
60
|
+
if suffix_chars > 0
|
|
61
|
+
else old_remainder
|
|
62
|
+
)
|
|
63
|
+
new_core = (
|
|
64
|
+
new_remainder[: len(new_remainder) - suffix_chars]
|
|
65
|
+
if suffix_chars > 0
|
|
66
|
+
else new_remainder
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
prefix = old_text[:prefix_chars]
|
|
70
|
+
suffix = old_remainder[-suffix_chars:] if suffix_chars > 0 else ""
|
|
71
|
+
|
|
72
|
+
shared_prefix_bytes = _utf8_bytes(prefix)
|
|
73
|
+
shared_suffix_bytes = _utf8_bytes(suffix)
|
|
74
|
+
shared_context_bytes = shared_prefix_bytes + shared_suffix_bytes
|
|
75
|
+
|
|
76
|
+
core_old_bytes = _utf8_bytes(old_core)
|
|
77
|
+
core_new_bytes = _utf8_bytes(new_core)
|
|
78
|
+
core_bytes = core_old_bytes + core_new_bytes
|
|
79
|
+
wrapper_payload_bytes = total - core_bytes
|
|
80
|
+
|
|
81
|
+
no_core_change = core_bytes == 0
|
|
82
|
+
inflation_ratio = None if no_core_change else total / core_bytes
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
"old_bytes": old_bytes,
|
|
86
|
+
"new_bytes": new_bytes,
|
|
87
|
+
"total_edit_bytes": total,
|
|
88
|
+
"shared_prefix_bytes": shared_prefix_bytes,
|
|
89
|
+
"shared_suffix_bytes": shared_suffix_bytes,
|
|
90
|
+
"shared_context_bytes": shared_context_bytes,
|
|
91
|
+
"core_old_bytes": core_old_bytes,
|
|
92
|
+
"core_new_bytes": core_new_bytes,
|
|
93
|
+
"core_bytes": core_bytes,
|
|
94
|
+
"wrapper_payload_bytes": wrapper_payload_bytes,
|
|
95
|
+
"inflation_ratio": inflation_ratio,
|
|
96
|
+
"no_core_change": no_core_change,
|
|
97
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Record command execution metrics to the tracking database.
|
|
2
|
+
|
|
3
|
+
Simple, self-contained utility — never raises, never blocks.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from code_muse.config import get_current_autosave_id
|
|
9
|
+
from code_muse.plugins.token_tracking.database import get_tracking_db
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _count_tokens(text: str) -> int:
|
|
15
|
+
"""Count whitespace-delimited tokens in text.
|
|
16
|
+
|
|
17
|
+
Simple heuristic — no tiktoken dependency. For empty text returns 0.
|
|
18
|
+
"""
|
|
19
|
+
return len(text.split()) if text else 0
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def record_command(
|
|
23
|
+
command: str,
|
|
24
|
+
raw_stdout: str,
|
|
25
|
+
raw_stderr: str,
|
|
26
|
+
compressed_stdout: str,
|
|
27
|
+
compressed_stderr: str,
|
|
28
|
+
category: str,
|
|
29
|
+
strategy: str,
|
|
30
|
+
exit_code: int = 0,
|
|
31
|
+
duration_ms: float = 0.0,
|
|
32
|
+
) -> None:
|
|
33
|
+
"""Insert a tracking record. Never raises — logs and returns on error."""
|
|
34
|
+
try:
|
|
35
|
+
raw_tokens = _count_tokens(f"{raw_stdout}\n{raw_stderr}")
|
|
36
|
+
compressed_tokens = _count_tokens(f"{compressed_stdout}\n{compressed_stderr}")
|
|
37
|
+
|
|
38
|
+
if raw_tokens == 0:
|
|
39
|
+
savings_pct = 0.0
|
|
40
|
+
else:
|
|
41
|
+
savings_pct = (raw_tokens - compressed_tokens) / raw_tokens * 100
|
|
42
|
+
|
|
43
|
+
get_tracking_db().insert(
|
|
44
|
+
command=command,
|
|
45
|
+
category=category,
|
|
46
|
+
strategy=strategy,
|
|
47
|
+
raw_tokens=raw_tokens,
|
|
48
|
+
compressed_tokens=compressed_tokens,
|
|
49
|
+
savings_pct=savings_pct,
|
|
50
|
+
session_id=get_current_autosave_id(),
|
|
51
|
+
exit_code=exit_code,
|
|
52
|
+
duration_ms=duration_ms,
|
|
53
|
+
)
|
|
54
|
+
except Exception:
|
|
55
|
+
logger.debug("record_command failed", exc_info=True)
|