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,203 @@
|
|
|
1
|
+
# cython: language_level=3
|
|
2
|
+
"""ScanCache core — thread-safe LRU cache for filesystem scan results."""
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
from collections import OrderedDict
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from code_muse.fs_scan_cache.ttl_policy import is_fresh
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class GlobMatch:
|
|
19
|
+
"""A single filesystem entry returned by a scan."""
|
|
20
|
+
|
|
21
|
+
path: str
|
|
22
|
+
file_type: str # "file" | "dir" | "symlink"
|
|
23
|
+
mtime: float
|
|
24
|
+
size: int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class ScanEntry:
|
|
29
|
+
"""Internal cached scan result with timestamp."""
|
|
30
|
+
|
|
31
|
+
entries: list[GlobMatch]
|
|
32
|
+
created_at: float
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class CacheStats:
|
|
37
|
+
"""Hit/miss/eviction statistics."""
|
|
38
|
+
|
|
39
|
+
hits: int = 0
|
|
40
|
+
misses: int = 0
|
|
41
|
+
evictions: int = 0
|
|
42
|
+
size: int = 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ScanCache:
|
|
46
|
+
"""Thread-safe LRU cache for filesystem scan results.
|
|
47
|
+
|
|
48
|
+
Keyed by a hashable partition tuple (typically ``(root, hidden, gitignore,
|
|
49
|
+
node_modules)``). Max 16 entries, LRU eviction. TTL-based freshness with
|
|
50
|
+
separate fast-recheck for empty results.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, max_entries: int = 16) -> None:
|
|
54
|
+
if max_entries < 1:
|
|
55
|
+
raise ValueError("max_entries must be positive")
|
|
56
|
+
self.max_entries = max_entries
|
|
57
|
+
# FREE-THREADED: Generic scan cache — may be accessed from sync or async code.
|
|
58
|
+
# Keep threading.Lock; migrate to asyncio.Lock only if all callers are async.
|
|
59
|
+
self._lock = threading.Lock()
|
|
60
|
+
self._cache: OrderedDict[tuple, ScanEntry] = OrderedDict()
|
|
61
|
+
self._stats = CacheStats()
|
|
62
|
+
|
|
63
|
+
def get_or_scan(
|
|
64
|
+
self,
|
|
65
|
+
key: tuple,
|
|
66
|
+
scanner_fn: Callable[[], list[GlobMatch]],
|
|
67
|
+
) -> tuple[list[GlobMatch], float]:
|
|
68
|
+
"""Return cached entries if fresh, otherwise call *scanner_fn*.
|
|
69
|
+
|
|
70
|
+
The scanner function is invoked **outside** the lock to avoid holding
|
|
71
|
+
it during I/O. A double-check is performed after re-acquiring the lock.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
``(entries, cache_age_ms)`` — *cache_age_ms* is ``0.0`` for a
|
|
75
|
+
fresh insertion, otherwise the monotonic age of the cached entry.
|
|
76
|
+
"""
|
|
77
|
+
cdef double now = time.monotonic()
|
|
78
|
+
cdef double age_ms
|
|
79
|
+
cdef double created
|
|
80
|
+
cdef object entry
|
|
81
|
+
cdef list scanned
|
|
82
|
+
cdef int evict_count
|
|
83
|
+
cdef object new_entry
|
|
84
|
+
|
|
85
|
+
with self._lock:
|
|
86
|
+
if key in self._cache:
|
|
87
|
+
entry = self._cache[key]
|
|
88
|
+
if is_fresh(entry, now):
|
|
89
|
+
self._cache.move_to_end(key)
|
|
90
|
+
age_ms = (now - entry.created_at) * 1000.0
|
|
91
|
+
self._stats.hits += 1
|
|
92
|
+
self._stats.size = len(self._cache)
|
|
93
|
+
return (entry.entries, age_ms)
|
|
94
|
+
# Stale — remove now so we don't return stale data later
|
|
95
|
+
del self._cache[key]
|
|
96
|
+
|
|
97
|
+
# Slow path: scan outside the lock
|
|
98
|
+
scanned = scanner_fn()
|
|
99
|
+
|
|
100
|
+
with self._lock:
|
|
101
|
+
# Double-check: another thread may have populated while we scanned
|
|
102
|
+
if key in self._cache:
|
|
103
|
+
entry = self._cache[key]
|
|
104
|
+
if is_fresh(entry, now):
|
|
105
|
+
self._cache.move_to_end(key)
|
|
106
|
+
age_ms = (now - entry.created_at) * 1000.0
|
|
107
|
+
self._stats.hits += 1
|
|
108
|
+
self._stats.size = len(self._cache)
|
|
109
|
+
return (entry.entries, age_ms)
|
|
110
|
+
del self._cache[key]
|
|
111
|
+
|
|
112
|
+
# Evict oldest if at capacity
|
|
113
|
+
evict_count = 0
|
|
114
|
+
while len(self._cache) >= self.max_entries:
|
|
115
|
+
self._cache.popitem(last=False)
|
|
116
|
+
evict_count += 1
|
|
117
|
+
if evict_count:
|
|
118
|
+
self._stats.evictions += evict_count
|
|
119
|
+
|
|
120
|
+
created = time.monotonic()
|
|
121
|
+
new_entry = ScanEntry(entries=scanned, created_at=created)
|
|
122
|
+
self._cache[key] = new_entry
|
|
123
|
+
self._stats.misses += 1
|
|
124
|
+
self._stats.size = len(self._cache)
|
|
125
|
+
return (new_entry.entries, 0.0)
|
|
126
|
+
|
|
127
|
+
def invalidate(self, root: str | None = None) -> None:
|
|
128
|
+
"""Remove cache entries affected by *root*.
|
|
129
|
+
|
|
130
|
+
If *root* is ``None``, clear the entire cache. Otherwise remove any
|
|
131
|
+
entry whose cached root is an ancestor of *root* or vice versa.
|
|
132
|
+
"""
|
|
133
|
+
cdef list keys_to_remove
|
|
134
|
+
cdef tuple key
|
|
135
|
+
cdef object target
|
|
136
|
+
cdef object cached_root
|
|
137
|
+
cdef bint cached_is_ancestor
|
|
138
|
+
cdef bint target_is_ancestor
|
|
139
|
+
|
|
140
|
+
with self._lock:
|
|
141
|
+
if root is None:
|
|
142
|
+
self._cache.clear()
|
|
143
|
+
self._stats.size = 0
|
|
144
|
+
logger.debug("ScanCache cleared entirely")
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
target = Path(root).resolve()
|
|
148
|
+
keys_to_remove = []
|
|
149
|
+
for key in list(self._cache.keys()):
|
|
150
|
+
cached_root = Path(key[0]).resolve()
|
|
151
|
+
# Ancestor check in both directions
|
|
152
|
+
try:
|
|
153
|
+
cached_is_ancestor = target.is_relative_to(cached_root)
|
|
154
|
+
except AttributeError:
|
|
155
|
+
# Python < 3.12 fallback
|
|
156
|
+
try:
|
|
157
|
+
cached_is_ancestor = (
|
|
158
|
+
target == cached_root or cached_root in target.parents
|
|
159
|
+
)
|
|
160
|
+
except ValueError:
|
|
161
|
+
cached_is_ancestor = False
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
target_is_ancestor = cached_root.is_relative_to(target)
|
|
165
|
+
except AttributeError:
|
|
166
|
+
try:
|
|
167
|
+
target_is_ancestor = (
|
|
168
|
+
cached_root == target or target in cached_root.parents
|
|
169
|
+
)
|
|
170
|
+
except ValueError:
|
|
171
|
+
target_is_ancestor = False
|
|
172
|
+
|
|
173
|
+
if cached_is_ancestor or target_is_ancestor:
|
|
174
|
+
keys_to_remove.append(key)
|
|
175
|
+
|
|
176
|
+
for key in keys_to_remove:
|
|
177
|
+
del self._cache[key]
|
|
178
|
+
logger.debug(f"ScanCache invalidated key {key}")
|
|
179
|
+
|
|
180
|
+
self._stats.size = len(self._cache)
|
|
181
|
+
|
|
182
|
+
def invalidate_for_path(self, path: str) -> None:
|
|
183
|
+
"""Invalidate cache entries related to *path*.
|
|
184
|
+
|
|
185
|
+
Convenience wrapper around :meth:`invalidate` that accepts a file
|
|
186
|
+
or directory path directly.
|
|
187
|
+
"""
|
|
188
|
+
self.invalidate(path)
|
|
189
|
+
|
|
190
|
+
def clear(self) -> None:
|
|
191
|
+
"""Remove all entries."""
|
|
192
|
+
self.invalidate(None)
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def stats(self) -> CacheStats:
|
|
196
|
+
"""Return a snapshot of cache statistics."""
|
|
197
|
+
with self._lock:
|
|
198
|
+
return CacheStats(
|
|
199
|
+
hits=self._stats.hits,
|
|
200
|
+
misses=self._stats.misses,
|
|
201
|
+
evictions=self._stats.evictions,
|
|
202
|
+
size=self._stats.size,
|
|
203
|
+
)
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""Cached wrappers around glob, grep, and find operations."""
|
|
2
|
+
|
|
3
|
+
import fnmatch
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from code_muse.fs_scan_cache.scan_cache_core import GlobMatch, ScanCache
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _resolve_root(root: str) -> Path:
|
|
12
|
+
return Path(root).expanduser().resolve()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _is_hidden(path: Path) -> bool:
|
|
16
|
+
"""Return True if any component of *path* starts with '.'."""
|
|
17
|
+
return any(
|
|
18
|
+
part.startswith(".") for part in path.parts if part not in (".", "..", os.sep)
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _should_skip(
|
|
23
|
+
path: Path,
|
|
24
|
+
*,
|
|
25
|
+
include_hidden: bool,
|
|
26
|
+
use_gitignore: bool,
|
|
27
|
+
skip_node_modules: bool,
|
|
28
|
+
) -> bool:
|
|
29
|
+
"""Return True if *path* should be excluded from scan results."""
|
|
30
|
+
if not include_hidden and _is_hidden(path):
|
|
31
|
+
return True
|
|
32
|
+
if skip_node_modules and "node_modules" in path.parts:
|
|
33
|
+
return True
|
|
34
|
+
if use_gitignore:
|
|
35
|
+
# Lightweight approximation: skip `.git` directory
|
|
36
|
+
if ".git" in path.parts:
|
|
37
|
+
return True
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _stat_entry(p: Path) -> tuple[str, float, int]:
|
|
42
|
+
"""Return (file_type, mtime, size) for *p*, falling back gracefully."""
|
|
43
|
+
try:
|
|
44
|
+
st = p.stat(follow_symlinks=False)
|
|
45
|
+
if p.is_symlink():
|
|
46
|
+
return "symlink", st.st_mtime, 0
|
|
47
|
+
if p.is_dir():
|
|
48
|
+
return "dir", st.st_mtime, 0
|
|
49
|
+
return "file", st.st_mtime, st.st_size
|
|
50
|
+
except OSError, ValueError:
|
|
51
|
+
return "file", 0.0, 0
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _glob_scanner(
|
|
55
|
+
pattern: str,
|
|
56
|
+
root: str,
|
|
57
|
+
*,
|
|
58
|
+
hidden: bool,
|
|
59
|
+
gitignore: bool,
|
|
60
|
+
node_modules: bool,
|
|
61
|
+
) -> list[GlobMatch]:
|
|
62
|
+
"""Perform a filesystem glob and return filtered GlobMatch entries."""
|
|
63
|
+
base = _resolve_root(root)
|
|
64
|
+
results: list[GlobMatch] = []
|
|
65
|
+
|
|
66
|
+
if "**" in pattern:
|
|
67
|
+
# Recursive glob via rglob
|
|
68
|
+
raw_pattern = pattern.replace("**/", "").replace("**", "")
|
|
69
|
+
# If pattern is just '**', match everything
|
|
70
|
+
if raw_pattern in {"", "*"}:
|
|
71
|
+
iterator = base.rglob("*")
|
|
72
|
+
else:
|
|
73
|
+
iterator = base.rglob(raw_pattern)
|
|
74
|
+
else:
|
|
75
|
+
iterator = base.glob(pattern)
|
|
76
|
+
|
|
77
|
+
for p in iterator:
|
|
78
|
+
try:
|
|
79
|
+
relative = p.relative_to(base)
|
|
80
|
+
except ValueError:
|
|
81
|
+
relative = p
|
|
82
|
+
if _should_skip(
|
|
83
|
+
relative,
|
|
84
|
+
include_hidden=hidden,
|
|
85
|
+
use_gitignore=gitignore,
|
|
86
|
+
skip_node_modules=node_modules,
|
|
87
|
+
):
|
|
88
|
+
continue
|
|
89
|
+
file_type, mtime, size = _stat_entry(p)
|
|
90
|
+
results.append(
|
|
91
|
+
GlobMatch(
|
|
92
|
+
path=str(p),
|
|
93
|
+
file_type=file_type,
|
|
94
|
+
mtime=mtime,
|
|
95
|
+
size=size,
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
return results
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _grep_scanner(
|
|
102
|
+
pattern: str,
|
|
103
|
+
root: str,
|
|
104
|
+
*,
|
|
105
|
+
hidden: bool,
|
|
106
|
+
gitignore: bool,
|
|
107
|
+
node_modules: bool,
|
|
108
|
+
) -> list[GlobMatch]:
|
|
109
|
+
"""Recursively search file contents with regex and return matching files."""
|
|
110
|
+
base = _resolve_root(root)
|
|
111
|
+
results: list[GlobMatch] = []
|
|
112
|
+
compiled = re.compile(pattern)
|
|
113
|
+
max_matches = 50
|
|
114
|
+
|
|
115
|
+
for p in base.rglob("*"):
|
|
116
|
+
try:
|
|
117
|
+
relative = p.relative_to(base)
|
|
118
|
+
except ValueError:
|
|
119
|
+
relative = p
|
|
120
|
+
if _should_skip(
|
|
121
|
+
relative,
|
|
122
|
+
include_hidden=hidden,
|
|
123
|
+
use_gitignore=gitignore,
|
|
124
|
+
skip_node_modules=node_modules,
|
|
125
|
+
):
|
|
126
|
+
continue
|
|
127
|
+
if not p.is_file() or p.is_symlink():
|
|
128
|
+
continue
|
|
129
|
+
try:
|
|
130
|
+
# Skip binary / very large files
|
|
131
|
+
size = p.stat().st_size
|
|
132
|
+
if size > 5 * 1024 * 1024:
|
|
133
|
+
continue
|
|
134
|
+
text = p.read_text(encoding="utf-8", errors="replace")
|
|
135
|
+
except OSError, UnicodeDecodeError:
|
|
136
|
+
continue
|
|
137
|
+
if compiled.search(text):
|
|
138
|
+
file_type, mtime, fsize = _stat_entry(p)
|
|
139
|
+
results.append(
|
|
140
|
+
GlobMatch(
|
|
141
|
+
path=str(p),
|
|
142
|
+
file_type=file_type,
|
|
143
|
+
mtime=mtime,
|
|
144
|
+
size=fsize,
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
if len(results) >= max_matches:
|
|
148
|
+
break
|
|
149
|
+
return results
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _find_scanner(
|
|
153
|
+
name: str,
|
|
154
|
+
root: str,
|
|
155
|
+
*,
|
|
156
|
+
hidden: bool,
|
|
157
|
+
gitignore: bool,
|
|
158
|
+
node_modules: bool,
|
|
159
|
+
) -> list[GlobMatch]:
|
|
160
|
+
"""Recursively find files/directories whose basename matches *name*."""
|
|
161
|
+
base = _resolve_root(root)
|
|
162
|
+
results: list[GlobMatch] = []
|
|
163
|
+
|
|
164
|
+
for p in base.rglob("*"):
|
|
165
|
+
try:
|
|
166
|
+
relative = p.relative_to(base)
|
|
167
|
+
except ValueError:
|
|
168
|
+
relative = p
|
|
169
|
+
if _should_skip(
|
|
170
|
+
relative,
|
|
171
|
+
include_hidden=hidden,
|
|
172
|
+
use_gitignore=gitignore,
|
|
173
|
+
skip_node_modules=node_modules,
|
|
174
|
+
):
|
|
175
|
+
continue
|
|
176
|
+
if not fnmatch.fnmatch(p.name, name):
|
|
177
|
+
continue
|
|
178
|
+
file_type, mtime, size = _stat_entry(p)
|
|
179
|
+
results.append(
|
|
180
|
+
GlobMatch(
|
|
181
|
+
path=str(p),
|
|
182
|
+
file_type=file_type,
|
|
183
|
+
mtime=mtime,
|
|
184
|
+
size=size,
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
return results
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# Module-level default cache used by the cached_* helpers.
|
|
191
|
+
_default_cache: ScanCache | None = None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _get_default_cache() -> ScanCache:
|
|
195
|
+
global _default_cache
|
|
196
|
+
if _default_cache is None:
|
|
197
|
+
_default_cache = ScanCache()
|
|
198
|
+
return _default_cache
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def cached_glob(
|
|
202
|
+
pattern: str,
|
|
203
|
+
root: str = ".",
|
|
204
|
+
hidden: bool = False,
|
|
205
|
+
gitignore: bool = True,
|
|
206
|
+
node_modules: bool = True,
|
|
207
|
+
cache: bool = False,
|
|
208
|
+
) -> tuple[list[GlobMatch], float | None]:
|
|
209
|
+
"""Glob with optional scan caching.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
``(entries, cache_age_ms)`` — *cache_age_ms* is ``None`` when
|
|
213
|
+
*cache* is ``False``.
|
|
214
|
+
"""
|
|
215
|
+
if not cache:
|
|
216
|
+
return (
|
|
217
|
+
_glob_scanner(
|
|
218
|
+
pattern,
|
|
219
|
+
root,
|
|
220
|
+
hidden=hidden,
|
|
221
|
+
gitignore=gitignore,
|
|
222
|
+
node_modules=node_modules,
|
|
223
|
+
),
|
|
224
|
+
None,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
key = (root, hidden, gitignore, node_modules, pattern)
|
|
228
|
+
sc = _get_default_cache()
|
|
229
|
+
entries, age = sc.get_or_scan(
|
|
230
|
+
key,
|
|
231
|
+
lambda: _glob_scanner(
|
|
232
|
+
pattern, root, hidden=hidden, gitignore=gitignore, node_modules=node_modules
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
return (entries, age)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def cached_grep(
|
|
239
|
+
pattern: str,
|
|
240
|
+
root: str = ".",
|
|
241
|
+
hidden: bool = False,
|
|
242
|
+
gitignore: bool = True,
|
|
243
|
+
node_modules: bool = True,
|
|
244
|
+
cache: bool = False,
|
|
245
|
+
) -> tuple[list[GlobMatch], float | None]:
|
|
246
|
+
"""Grep with optional scan caching.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
``(entries, cache_age_ms)`` — *cache_age_ms* is ``None`` when
|
|
250
|
+
*cache* is ``False``.
|
|
251
|
+
"""
|
|
252
|
+
if not cache:
|
|
253
|
+
return (
|
|
254
|
+
_grep_scanner(
|
|
255
|
+
pattern,
|
|
256
|
+
root,
|
|
257
|
+
hidden=hidden,
|
|
258
|
+
gitignore=gitignore,
|
|
259
|
+
node_modules=node_modules,
|
|
260
|
+
),
|
|
261
|
+
None,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
key = (root, hidden, gitignore, node_modules, pattern)
|
|
265
|
+
sc = _get_default_cache()
|
|
266
|
+
entries, age = sc.get_or_scan(
|
|
267
|
+
key,
|
|
268
|
+
lambda: _grep_scanner(
|
|
269
|
+
pattern, root, hidden=hidden, gitignore=gitignore, node_modules=node_modules
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
return (entries, age)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def cached_find(
|
|
276
|
+
name: str,
|
|
277
|
+
root: str = ".",
|
|
278
|
+
hidden: bool = False,
|
|
279
|
+
gitignore: bool = True,
|
|
280
|
+
node_modules: bool = True,
|
|
281
|
+
cache: bool = False,
|
|
282
|
+
) -> tuple[list[GlobMatch], float | None]:
|
|
283
|
+
"""Filename search with optional scan caching.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
``(entries, cache_age_ms)`` — *cache_age_ms* is ``None`` when
|
|
287
|
+
*cache* is ``False``.
|
|
288
|
+
"""
|
|
289
|
+
if not cache:
|
|
290
|
+
return (
|
|
291
|
+
_find_scanner(
|
|
292
|
+
name,
|
|
293
|
+
root,
|
|
294
|
+
hidden=hidden,
|
|
295
|
+
gitignore=gitignore,
|
|
296
|
+
node_modules=node_modules,
|
|
297
|
+
),
|
|
298
|
+
None,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
key = (root, hidden, gitignore, node_modules, name)
|
|
302
|
+
sc = _get_default_cache()
|
|
303
|
+
entries, age = sc.get_or_scan(
|
|
304
|
+
key,
|
|
305
|
+
lambda: _find_scanner(
|
|
306
|
+
name, root, hidden=hidden, gitignore=gitignore, node_modules=node_modules
|
|
307
|
+
),
|
|
308
|
+
)
|
|
309
|
+
return (entries, age)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""TTL policy for ScanCache entries."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
if typing.TYPE_CHECKING:
|
|
7
|
+
from code_muse.fs_scan_cache.scan_cache_core import ScanEntry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def env_uint(name: str, default: int) -> int:
|
|
11
|
+
"""Read an unsigned integer from an environment variable.
|
|
12
|
+
|
|
13
|
+
Returns *default* if the variable is missing, empty, or not a valid
|
|
14
|
+
non-negative integer.
|
|
15
|
+
"""
|
|
16
|
+
raw = os.environ.get(name)
|
|
17
|
+
if raw is None:
|
|
18
|
+
return default
|
|
19
|
+
try:
|
|
20
|
+
value = int(raw)
|
|
21
|
+
except ValueError:
|
|
22
|
+
return default
|
|
23
|
+
return value if value >= 0 else default
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
CACHE_TTL_MS: int = env_uint("FS_SCAN_CACHE_TTL_MS", 1000)
|
|
27
|
+
EMPTY_RECHECK_MS: int = env_uint("FS_SCAN_EMPTY_RECHECK_MS", 200)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_fresh(entry: ScanEntry, now: float) -> bool:
|
|
31
|
+
"""Return ``True`` if *entry* has not exceeded its TTL.
|
|
32
|
+
|
|
33
|
+
Rules:
|
|
34
|
+
* If ``CACHE_TTL_MS == 0``: always stale (cache bypass).
|
|
35
|
+
* If *entry* has no items: age must be < ``EMPTY_RECHECK_MS``.
|
|
36
|
+
* If *entry* has items: age must be < ``CACHE_TTL_MS``.
|
|
37
|
+
"""
|
|
38
|
+
if CACHE_TTL_MS == 0:
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
age_ms = (now - entry.created_at) * 1000.0
|
|
42
|
+
if not entry.entries:
|
|
43
|
+
return age_ms < EMPTY_RECHECK_MS
|
|
44
|
+
return age_ms < CACHE_TTL_MS
|