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,382 @@
|
|
|
1
|
+
"""Stub extraction engine for the Autonomous Memory Pipeline.
|
|
2
|
+
|
|
3
|
+
Reads a session's messages file and produces a basic markdown summary.
|
|
4
|
+
Future iterations will drive this via a headless LLM agent.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from datetime import UTC, datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
_MEMORY_RELEVANCE_THRESHOLD = float(
|
|
19
|
+
os.environ.get("MEMORY_RELEVANCE_THRESHOLD", "0.15")
|
|
20
|
+
)
|
|
21
|
+
_MEMORY_MIN_KEEP = int(os.environ.get("MEMORY_MIN_KEEP_CHUNKS", "3"))
|
|
22
|
+
|
|
23
|
+
# Optional BM25 scorer — gracefully degrades if module missing
|
|
24
|
+
_BM25_AVAILABLE = True
|
|
25
|
+
try:
|
|
26
|
+
from code_muse.plugins.autonomous_memory.bm25_scorer import (
|
|
27
|
+
BM25Scorer,
|
|
28
|
+
select_top_chunks,
|
|
29
|
+
)
|
|
30
|
+
except Exception:
|
|
31
|
+
_BM25_AVAILABLE = False
|
|
32
|
+
|
|
33
|
+
_ROLE_RE = re.compile(r'"role"\s*:\s*"(\w+)"')
|
|
34
|
+
_TOOL_RE = re.compile(r'"name"\s*:\s*"([^"]+)"')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class ExtractionResult:
|
|
39
|
+
"""Output of extracting knowledge from a single session."""
|
|
40
|
+
|
|
41
|
+
session_path: str
|
|
42
|
+
raw_memory: str
|
|
43
|
+
synopsis: str
|
|
44
|
+
extracted_at: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _parse_messages_jsonl(path: Path) -> list[dict[str, Any]]:
|
|
48
|
+
"""Best-effort parse of a JSONL messages file."""
|
|
49
|
+
messages: list[dict[str, Any]] = []
|
|
50
|
+
try:
|
|
51
|
+
with path.open("r", encoding="utf-8") as fh:
|
|
52
|
+
for line in fh:
|
|
53
|
+
line = line.strip()
|
|
54
|
+
if not line:
|
|
55
|
+
continue
|
|
56
|
+
try:
|
|
57
|
+
messages.append(json.loads(line))
|
|
58
|
+
except json.JSONDecodeError:
|
|
59
|
+
continue
|
|
60
|
+
except Exception as exc:
|
|
61
|
+
logger.warning(f"Failed to read messages file {path}: {exc}")
|
|
62
|
+
return messages
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _parse_messages_json(path: Path) -> list[dict[str, Any]]:
|
|
66
|
+
"""Best-effort parse of a JSON array messages file."""
|
|
67
|
+
try:
|
|
68
|
+
with path.open("r", encoding="utf-8") as fh:
|
|
69
|
+
data = json.load(fh)
|
|
70
|
+
if isinstance(data, list):
|
|
71
|
+
return data
|
|
72
|
+
except Exception as exc:
|
|
73
|
+
logger.warning(f"Failed to read messages file {path}: {exc}")
|
|
74
|
+
return []
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _load_messages(path: Path) -> list[dict[str, Any]]:
|
|
78
|
+
"""Load messages from either JSONL or JSON array format."""
|
|
79
|
+
if path.suffix == ".jsonl":
|
|
80
|
+
return _parse_messages_jsonl(path)
|
|
81
|
+
return _parse_messages_json(path)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _count_roles(messages: list[dict[str, Any]]) -> dict[str, int]:
|
|
85
|
+
"""Tally message roles."""
|
|
86
|
+
counts: dict[str, int] = {}
|
|
87
|
+
for msg in messages:
|
|
88
|
+
role = msg.get("role", "unknown")
|
|
89
|
+
counts[role] = counts.get(role, 0) + 1
|
|
90
|
+
return counts
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _extract_tools(messages: list[dict[str, Any]]) -> set[str]:
|
|
94
|
+
"""Extract unique tool names from tool-call messages."""
|
|
95
|
+
tools: set[str] = set()
|
|
96
|
+
for msg in messages:
|
|
97
|
+
if msg.get("role") != "tool":
|
|
98
|
+
# Also look inside assistant messages for tool_calls
|
|
99
|
+
tool_calls = msg.get("tool_calls", [])
|
|
100
|
+
if isinstance(tool_calls, list):
|
|
101
|
+
for tc in tool_calls:
|
|
102
|
+
name = (
|
|
103
|
+
tc.get("function", {}).get("name")
|
|
104
|
+
if isinstance(tc, dict)
|
|
105
|
+
else None
|
|
106
|
+
)
|
|
107
|
+
if name:
|
|
108
|
+
tools.add(name)
|
|
109
|
+
elif isinstance(tc, dict):
|
|
110
|
+
name = tc.get("name")
|
|
111
|
+
if name:
|
|
112
|
+
tools.add(name)
|
|
113
|
+
continue
|
|
114
|
+
content = msg.get("content", "")
|
|
115
|
+
if isinstance(content, str):
|
|
116
|
+
match = _TOOL_RE.search(content)
|
|
117
|
+
if match:
|
|
118
|
+
tools.add(match.group(1))
|
|
119
|
+
name = msg.get("name") or msg.get("tool_name")
|
|
120
|
+
if isinstance(name, str):
|
|
121
|
+
tools.add(name)
|
|
122
|
+
return tools
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _collect_project_context(cwd: str | None = None) -> str:
|
|
126
|
+
"""Collect project context from key files in the working directory.
|
|
127
|
+
|
|
128
|
+
Reads README, pyproject.toml/package.json, and key source files
|
|
129
|
+
to build a context string for relevance scoring.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
cwd: Working directory. Uses current working dir if None.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Context string (up to 2000 chars).
|
|
136
|
+
"""
|
|
137
|
+
root = Path(cwd) if cwd else Path.cwd()
|
|
138
|
+
parts: list[str] = []
|
|
139
|
+
|
|
140
|
+
# Project name from directory
|
|
141
|
+
parts.append(f"Project: {root.name}")
|
|
142
|
+
|
|
143
|
+
# Read README (first 500 chars)
|
|
144
|
+
readme_paths = [
|
|
145
|
+
root / "README.md",
|
|
146
|
+
root / "README.rst",
|
|
147
|
+
root / "README",
|
|
148
|
+
]
|
|
149
|
+
for rp in readme_paths:
|
|
150
|
+
if rp.exists():
|
|
151
|
+
try:
|
|
152
|
+
text = rp.read_text(encoding="utf-8", errors="replace")
|
|
153
|
+
parts.append(f"README: {text[:500]}")
|
|
154
|
+
except Exception:
|
|
155
|
+
pass
|
|
156
|
+
break
|
|
157
|
+
|
|
158
|
+
# Read project config
|
|
159
|
+
config_paths = [
|
|
160
|
+
root / "pyproject.toml",
|
|
161
|
+
root / "package.json",
|
|
162
|
+
root / "Cargo.toml",
|
|
163
|
+
root / "go.mod",
|
|
164
|
+
]
|
|
165
|
+
for cp in config_paths:
|
|
166
|
+
if cp.exists():
|
|
167
|
+
try:
|
|
168
|
+
text = cp.read_text(encoding="utf-8", errors="replace")
|
|
169
|
+
parts.append(f"Config ({cp.name}): {text[:300]}")
|
|
170
|
+
except Exception:
|
|
171
|
+
pass
|
|
172
|
+
break
|
|
173
|
+
|
|
174
|
+
# Key source files (first 200 chars each, up to 5 files)
|
|
175
|
+
src_dirs = [root / "src", root / "lib", root / "app", root]
|
|
176
|
+
seen = 0
|
|
177
|
+
for src_dir in src_dirs:
|
|
178
|
+
if not src_dir.exists():
|
|
179
|
+
continue
|
|
180
|
+
for ext in (".py", ".js", ".ts", ".go", ".rs"):
|
|
181
|
+
for sf in sorted(src_dir.rglob(f"*{ext}"))[:3]:
|
|
182
|
+
try:
|
|
183
|
+
text = sf.read_text(encoding="utf-8", errors="replace")
|
|
184
|
+
parts.append(f"Source ({sf.name}): {text[:200]}")
|
|
185
|
+
seen += 1
|
|
186
|
+
if seen >= 5:
|
|
187
|
+
break
|
|
188
|
+
except Exception:
|
|
189
|
+
pass
|
|
190
|
+
if seen >= 5:
|
|
191
|
+
break
|
|
192
|
+
if seen >= 5:
|
|
193
|
+
break
|
|
194
|
+
|
|
195
|
+
context = "\n\n".join(parts)
|
|
196
|
+
return context[:2000]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _split_into_chunks(text: str, max_chunk_lines: int = 30) -> list[str]:
|
|
200
|
+
"""Split session transcript into turn-level chunks.
|
|
201
|
+
|
|
202
|
+
Each chunk is roughly one conversational turn (user/assistant pair)
|
|
203
|
+
or a block of up to max_chunk_lines.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
text: Full transcript text.
|
|
207
|
+
max_chunk_lines: Maximum lines per chunk.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
List of chunk strings.
|
|
211
|
+
"""
|
|
212
|
+
lines = text.split("\n")
|
|
213
|
+
chunks: list[str] = []
|
|
214
|
+
current: list[str] = []
|
|
215
|
+
|
|
216
|
+
for line in lines:
|
|
217
|
+
current.append(line)
|
|
218
|
+
# Split on common turn boundaries
|
|
219
|
+
if (
|
|
220
|
+
line.strip().startswith(("User:", "Assistant:", "Human:", "AI:", ">>>"))
|
|
221
|
+
or line.strip().startswith("---")
|
|
222
|
+
or len(current) >= max_chunk_lines
|
|
223
|
+
):
|
|
224
|
+
chunk_text = "\n".join(current).strip()
|
|
225
|
+
if chunk_text and len(chunk_text) > 20:
|
|
226
|
+
chunks.append(chunk_text)
|
|
227
|
+
current = []
|
|
228
|
+
|
|
229
|
+
# Don't forget last chunk
|
|
230
|
+
if current:
|
|
231
|
+
chunk_text = "\n".join(current).strip()
|
|
232
|
+
if chunk_text and len(chunk_text) > 20:
|
|
233
|
+
chunks.append(chunk_text)
|
|
234
|
+
|
|
235
|
+
# If no chunks found, return whole text as one chunk
|
|
236
|
+
if not chunks:
|
|
237
|
+
return [text]
|
|
238
|
+
|
|
239
|
+
return chunks
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _messages_to_transcript(messages: list[dict[str, Any]]) -> str:
|
|
243
|
+
"""Convert message list to a plain-text transcript."""
|
|
244
|
+
lines: list[str] = []
|
|
245
|
+
for msg in messages:
|
|
246
|
+
role = msg.get("role", "unknown")
|
|
247
|
+
content = msg.get("content", "")
|
|
248
|
+
if isinstance(content, str) and content.strip():
|
|
249
|
+
lines.append(f"{role.capitalize()}: {content.strip()}")
|
|
250
|
+
# Include tool call summaries
|
|
251
|
+
tool_calls = msg.get("tool_calls", [])
|
|
252
|
+
if isinstance(tool_calls, list):
|
|
253
|
+
for tc in tool_calls:
|
|
254
|
+
if not isinstance(tc, dict):
|
|
255
|
+
continue
|
|
256
|
+
name = tc.get("function", {}).get("name") or tc.get("name")
|
|
257
|
+
if name:
|
|
258
|
+
lines.append(f"Tool: {name}")
|
|
259
|
+
return "\n".join(lines)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _extract_topics(messages: list[dict[str, Any]]) -> list[str]:
|
|
263
|
+
"""Extract user message content snippets as 'topics'."""
|
|
264
|
+
topics: list[str] = []
|
|
265
|
+
for msg in messages:
|
|
266
|
+
if msg.get("role") != "user":
|
|
267
|
+
continue
|
|
268
|
+
content = msg.get("content", "")
|
|
269
|
+
if isinstance(content, str) and content.strip():
|
|
270
|
+
# Truncate long messages to first 120 chars for brevity
|
|
271
|
+
snippet = content.strip().replace("\n", " ")
|
|
272
|
+
if len(snippet) > 120:
|
|
273
|
+
snippet = snippet[:117] + "..."
|
|
274
|
+
topics.append(snippet)
|
|
275
|
+
# Deduplicate while preserving order
|
|
276
|
+
seen: set[str] = set()
|
|
277
|
+
deduped: list[str] = []
|
|
278
|
+
for t in topics:
|
|
279
|
+
if t not in seen:
|
|
280
|
+
seen.add(t)
|
|
281
|
+
deduped.append(t)
|
|
282
|
+
return deduped[:20] # Cap at 20 topics
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def extract_session_knowledge(session_path: Path) -> ExtractionResult | None:
|
|
286
|
+
"""Extract a basic memory summary from a session directory.
|
|
287
|
+
|
|
288
|
+
This is a stub implementation. Future work will replace it with a
|
|
289
|
+
headless LLM agent that performs deeper semantic extraction.
|
|
290
|
+
"""
|
|
291
|
+
try:
|
|
292
|
+
messages_file = None
|
|
293
|
+
for candidate in (
|
|
294
|
+
session_path / "messages.json",
|
|
295
|
+
*session_path.glob("*.jsonl"),
|
|
296
|
+
):
|
|
297
|
+
if candidate.exists():
|
|
298
|
+
messages_file = candidate
|
|
299
|
+
break
|
|
300
|
+
|
|
301
|
+
if messages_file is None:
|
|
302
|
+
logger.warning(f"No messages file found in {session_path}")
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
messages = _load_messages(messages_file)
|
|
306
|
+
if not messages:
|
|
307
|
+
logger.warning(f"Empty or unreadable messages file in {session_path}")
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
# --- Relevance scoring (added Epic 022) ---
|
|
311
|
+
# Convert messages to transcript, split into chunks, score against context
|
|
312
|
+
transcript = _messages_to_transcript(messages)
|
|
313
|
+
session_chunks = _split_into_chunks(transcript)
|
|
314
|
+
|
|
315
|
+
relevant_messages = messages
|
|
316
|
+
if _BM25_AVAILABLE and session_chunks:
|
|
317
|
+
try:
|
|
318
|
+
project_context = _collect_project_context()
|
|
319
|
+
scorer = BM25Scorer()
|
|
320
|
+
scorer.fit(session_chunks)
|
|
321
|
+
scores = scorer.score_batch(project_context, session_chunks)
|
|
322
|
+
relevant_chunks = select_top_chunks(
|
|
323
|
+
session_chunks,
|
|
324
|
+
scores,
|
|
325
|
+
threshold=_MEMORY_RELEVANCE_THRESHOLD,
|
|
326
|
+
min_keep=_MEMORY_MIN_KEEP,
|
|
327
|
+
)
|
|
328
|
+
logger.debug(
|
|
329
|
+
"Relevance scoring: %d/%d chunks kept (%.0f%%) for extraction",
|
|
330
|
+
len(relevant_chunks),
|
|
331
|
+
len(session_chunks),
|
|
332
|
+
len(relevant_chunks) / max(len(session_chunks), 1) * 100,
|
|
333
|
+
)
|
|
334
|
+
# Map relevant chunks back to messages for topic extraction
|
|
335
|
+
# A message is "relevant" if its content appears in a kept chunk
|
|
336
|
+
relevant_text = "\n".join(relevant_chunks)
|
|
337
|
+
relevant_messages = [
|
|
338
|
+
msg
|
|
339
|
+
for msg in messages
|
|
340
|
+
if isinstance(msg.get("content", ""), str)
|
|
341
|
+
and msg.get("content", "") in relevant_text
|
|
342
|
+
]
|
|
343
|
+
# Ensure at least some messages remain for topic extraction
|
|
344
|
+
if not relevant_messages:
|
|
345
|
+
relevant_messages = messages[:_MEMORY_MIN_KEEP]
|
|
346
|
+
except Exception as exc:
|
|
347
|
+
logger.warning(
|
|
348
|
+
f"BM25 scoring failed, falling back to full transcript: {exc}"
|
|
349
|
+
)
|
|
350
|
+
relevant_messages = messages
|
|
351
|
+
# --- End relevance scoring ---
|
|
352
|
+
|
|
353
|
+
counts = _count_roles(messages)
|
|
354
|
+
tools = _extract_tools(messages)
|
|
355
|
+
topics = _extract_topics(relevant_messages)
|
|
356
|
+
|
|
357
|
+
user_count = counts.get("user", 0)
|
|
358
|
+
assistant_count = counts.get("assistant", 0)
|
|
359
|
+
tool_count = counts.get("tool", 0)
|
|
360
|
+
|
|
361
|
+
lines = [
|
|
362
|
+
"## Session Summary",
|
|
363
|
+
f"- Messages: {user_count} user, {assistant_count} assistant, {tool_count} tool",
|
|
364
|
+
]
|
|
365
|
+
if tools:
|
|
366
|
+
lines.append(f"- Tools: {', '.join(sorted(tools))}")
|
|
367
|
+
if topics:
|
|
368
|
+
lines.append(f"- Topics: {'; '.join(topics)}")
|
|
369
|
+
|
|
370
|
+
raw_memory = "\n".join(lines)
|
|
371
|
+
synopsis = f"Session with {user_count} user messages, {len(tools)} tools, {len(topics)} topics"
|
|
372
|
+
extracted_at = datetime.now(UTC).isoformat()
|
|
373
|
+
|
|
374
|
+
return ExtractionResult(
|
|
375
|
+
session_path=str(session_path),
|
|
376
|
+
raw_memory=raw_memory,
|
|
377
|
+
synopsis=synopsis,
|
|
378
|
+
extracted_at=extracted_at,
|
|
379
|
+
)
|
|
380
|
+
except Exception as exc:
|
|
381
|
+
logger.warning(f"Extraction failed for {session_path}: {exc}")
|
|
382
|
+
return None
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Lease-based lock for the Autonomous Memory Pipeline.
|
|
2
|
+
|
|
3
|
+
Prevents concurrent memory extraction / consolidation jobs from
|
|
4
|
+
stepping on each other across multiple processes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import time
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _is_pid_running(pid: int) -> bool:
|
|
18
|
+
"""Return ``True`` if process ``pid`` is alive."""
|
|
19
|
+
try:
|
|
20
|
+
os.kill(pid, 0)
|
|
21
|
+
except OSError:
|
|
22
|
+
return False
|
|
23
|
+
else:
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class LeaseHandle:
|
|
29
|
+
"""Token representing a held memory lease."""
|
|
30
|
+
|
|
31
|
+
lock_path: Path
|
|
32
|
+
pid: int
|
|
33
|
+
acquired_at: float
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def acquire_memory_lease(
|
|
37
|
+
memory_dir: Path, timeout_minutes: int = 30
|
|
38
|
+
) -> LeaseHandle | None:
|
|
39
|
+
"""Attempt to acquire the memory-processing lease.
|
|
40
|
+
|
|
41
|
+
Returns a :class:`LeaseHandle` on success, or ``None`` if the lease
|
|
42
|
+
is already held by a living process within the timeout window.
|
|
43
|
+
"""
|
|
44
|
+
memory_dir.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
lock_path = memory_dir / ".memory_lease"
|
|
46
|
+
|
|
47
|
+
# Check for existing lease
|
|
48
|
+
if lock_path.exists():
|
|
49
|
+
try:
|
|
50
|
+
with lock_path.open("r", encoding="utf-8") as fh:
|
|
51
|
+
data = json.load(fh)
|
|
52
|
+
old_pid = int(data.get("pid", 0))
|
|
53
|
+
acquired_at = float(data.get("acquired_at", 0))
|
|
54
|
+
except Exception:
|
|
55
|
+
old_pid = 0
|
|
56
|
+
acquired_at = 0
|
|
57
|
+
|
|
58
|
+
now = time.time()
|
|
59
|
+
expired = (now - acquired_at) > (timeout_minutes * 60)
|
|
60
|
+
|
|
61
|
+
if old_pid and _is_pid_running(old_pid) and not expired:
|
|
62
|
+
logger.debug(f"Memory lease held by PID {old_pid}")
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
# Stale lease — break it
|
|
66
|
+
try:
|
|
67
|
+
lock_path.unlink()
|
|
68
|
+
logger.debug("Broke stale memory lease")
|
|
69
|
+
except OSError as exc:
|
|
70
|
+
logger.warning(f"Could not remove stale lock {lock_path}: {exc}")
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
# Write new lease
|
|
74
|
+
my_pid = os.getpid()
|
|
75
|
+
acquired_at = time.time()
|
|
76
|
+
try:
|
|
77
|
+
with lock_path.open("w", encoding="utf-8") as fh:
|
|
78
|
+
json.dump({"pid": my_pid, "acquired_at": acquired_at}, fh)
|
|
79
|
+
except OSError as exc:
|
|
80
|
+
logger.warning(f"Could not write memory lease {lock_path}: {exc}")
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
return LeaseHandle(lock_path=lock_path, pid=my_pid, acquired_at=acquired_at)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def release_lease(handle: LeaseHandle) -> None:
|
|
87
|
+
"""Release a previously acquired memory lease."""
|
|
88
|
+
if not handle.lock_path.exists():
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
with handle.lock_path.open("r", encoding="utf-8") as fh:
|
|
93
|
+
data = json.load(fh)
|
|
94
|
+
current_pid = int(data.get("pid", 0))
|
|
95
|
+
except Exception:
|
|
96
|
+
current_pid = 0
|
|
97
|
+
|
|
98
|
+
if current_pid == handle.pid:
|
|
99
|
+
try:
|
|
100
|
+
handle.lock_path.unlink()
|
|
101
|
+
logger.debug("Released memory lease")
|
|
102
|
+
except OSError as exc:
|
|
103
|
+
logger.warning(f"Could not release lease {handle.lock_path}: {exc}")
|
|
104
|
+
else:
|
|
105
|
+
logger.debug("Lease PID mismatch; not releasing")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Memory injection for the Autonomous Memory Pipeline.
|
|
2
|
+
|
|
3
|
+
Loads the current project's memory summary (if fresh) and appends it to
|
|
4
|
+
system prompts for heuristic context.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
from .session_scanner import get_memory_dir, get_project_hash
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
FRESHNESS_DAYS = 7
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_memory_injection(cwd: str | None = None) -> str | None:
|
|
19
|
+
"""Load the memory summary for the current project if it is fresh.
|
|
20
|
+
|
|
21
|
+
Returns the memory text, or ``None`` if the file is missing or stale.
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
cwd = cwd or os.getcwd()
|
|
25
|
+
project_hash = get_project_hash(cwd)
|
|
26
|
+
memory_dir = get_memory_dir(project_hash)
|
|
27
|
+
summary_path = memory_dir / "memory_summary.md"
|
|
28
|
+
|
|
29
|
+
if not summary_path.exists():
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
mtime = summary_path.stat().st_mtime
|
|
33
|
+
age_days = (time.time() - mtime) / 86_400
|
|
34
|
+
if age_days > FRESHNESS_DAYS:
|
|
35
|
+
logger.debug(f"Memory summary stale ({age_days:.1f} days old)")
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
text = summary_path.read_text(encoding="utf-8")
|
|
39
|
+
logger.info(f"Loaded memory injection from {summary_path}")
|
|
40
|
+
return text
|
|
41
|
+
except Exception as exc:
|
|
42
|
+
logger.warning(f"Failed to load memory injection: {exc}")
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def inject_into_system_prompt(base_prompt: str, memory_text: str) -> str:
|
|
47
|
+
"""Append a memory section to ``base_prompt``.
|
|
48
|
+
|
|
49
|
+
Returns the combined prompt string.
|
|
50
|
+
"""
|
|
51
|
+
section = (
|
|
52
|
+
"\n\n## Memory Guidance\n\n"
|
|
53
|
+
"The following is accumulated project knowledge from past sessions.\n"
|
|
54
|
+
"Treat it as heuristic context, not authoritative fact. Always prefer\n"
|
|
55
|
+
"current repo evidence over conflicting memory. Cite the memory path\n"
|
|
56
|
+
"(MEMORY.md) when you use remembered information.\n\n"
|
|
57
|
+
f"{memory_text}"
|
|
58
|
+
)
|
|
59
|
+
return base_prompt + section
|