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.
Files changed (394) hide show
  1. code_muse/__init__.py +26 -0
  2. code_muse/__main__.py +10 -0
  3. code_muse/agents/__init__.py +31 -0
  4. code_muse/agents/_builder.py +214 -0
  5. code_muse/agents/_compaction.py +506 -0
  6. code_muse/agents/_diagnostics.py +171 -0
  7. code_muse/agents/_history.py +382 -0
  8. code_muse/agents/_key_listeners.py +148 -0
  9. code_muse/agents/_non_streaming_render.py +148 -0
  10. code_muse/agents/_runtime.py +596 -0
  11. code_muse/agents/agent_creator_agent.py +603 -0
  12. code_muse/agents/agent_helios.py +47 -0
  13. code_muse/agents/agent_manager.py +740 -0
  14. code_muse/agents/agent_muse.py +78 -0
  15. code_muse/agents/agent_planning.py +44 -0
  16. code_muse/agents/agent_qa_melpomene.py +207 -0
  17. code_muse/agents/base_agent.py +194 -0
  18. code_muse/agents/event_stream_handler.py +361 -0
  19. code_muse/agents/json_agent.py +201 -0
  20. code_muse/agents/prompt_v3.py +521 -0
  21. code_muse/agents/subagent_stream_handler.py +273 -0
  22. code_muse/callbacks.py +941 -0
  23. code_muse/chatgpt_codex_client.py +333 -0
  24. code_muse/claude_cache_client.py +853 -0
  25. code_muse/cli_runner/__init__.py +319 -0
  26. code_muse/cli_runner/args.py +63 -0
  27. code_muse/cli_runner/loop.py +510 -0
  28. code_muse/cli_runner/resume.py +72 -0
  29. code_muse/cli_runner/runner.py +161 -0
  30. code_muse/command_line/__init__.py +1 -0
  31. code_muse/command_line/add_model_menu.py +1331 -0
  32. code_muse/command_line/agent_menu.py +674 -0
  33. code_muse/command_line/attachments.py +397 -0
  34. code_muse/command_line/autosave_menu.py +709 -0
  35. code_muse/command_line/clipboard.py +528 -0
  36. code_muse/command_line/colors_menu.py +530 -0
  37. code_muse/command_line/command_handler.py +262 -0
  38. code_muse/command_line/command_registry.py +150 -0
  39. code_muse/command_line/config_commands.py +711 -0
  40. code_muse/command_line/core_commands.py +740 -0
  41. code_muse/command_line/diff_menu.py +865 -0
  42. code_muse/command_line/file_path_completion.py +73 -0
  43. code_muse/command_line/load_context_completion.py +57 -0
  44. code_muse/command_line/model_picker_completion.py +512 -0
  45. code_muse/command_line/model_settings_menu.py +983 -0
  46. code_muse/command_line/onboarding_slides.py +162 -0
  47. code_muse/command_line/onboarding_wizard.py +337 -0
  48. code_muse/command_line/pagination.py +41 -0
  49. code_muse/command_line/pin_command_completion.py +329 -0
  50. code_muse/command_line/prompt_toolkit_completion.py +886 -0
  51. code_muse/command_line/session_commands.py +304 -0
  52. code_muse/command_line/shell_passthrough.py +145 -0
  53. code_muse/command_line/skills_completion.py +158 -0
  54. code_muse/command_line/types.py +18 -0
  55. code_muse/command_line/uc_menu.py +908 -0
  56. code_muse/command_line/utils.py +105 -0
  57. code_muse/command_line/wiggum_state.py +77 -0
  58. code_muse/config.py +1138 -0
  59. code_muse/config_agent.py +168 -0
  60. code_muse/config_appearance.py +241 -0
  61. code_muse/config_model.py +357 -0
  62. code_muse/config_security.py +73 -0
  63. code_muse/error_logging.py +132 -0
  64. code_muse/evals/__init__.py +35 -0
  65. code_muse/evals/eval_helpers.py +81 -0
  66. code_muse/evals/eval_runner.py +299 -0
  67. code_muse/evals/sample_evals/__init__.py +1 -0
  68. code_muse/evals/sample_evals/eval_frugal_reads.py +59 -0
  69. code_muse/evals/sample_evals/eval_memory_planning.py +31 -0
  70. code_muse/evals/sample_evals/eval_shell_efficiency.py +39 -0
  71. code_muse/evals/sample_evals/eval_tool_masking.py +33 -0
  72. code_muse/fs_scan_cache/__init__.py +31 -0
  73. code_muse/fs_scan_cache/invalidation_hooks.py +89 -0
  74. code_muse/fs_scan_cache/scan_cache_core.cpython-314-darwin.so +0 -0
  75. code_muse/fs_scan_cache/scan_cache_core.pyx +203 -0
  76. code_muse/fs_scan_cache/tool_integration.py +309 -0
  77. code_muse/fs_scan_cache/ttl_policy.py +44 -0
  78. code_muse/gemini_code_assist.py +383 -0
  79. code_muse/gemini_model.py +838 -0
  80. code_muse/hook_engine/README.md +105 -0
  81. code_muse/hook_engine/__init__.py +21 -0
  82. code_muse/hook_engine/aliases.py +153 -0
  83. code_muse/hook_engine/engine.py +221 -0
  84. code_muse/hook_engine/executor.py +347 -0
  85. code_muse/hook_engine/matcher.py +154 -0
  86. code_muse/hook_engine/models.py +245 -0
  87. code_muse/hook_engine/registry.py +114 -0
  88. code_muse/hook_engine/trust.py +268 -0
  89. code_muse/hook_engine/validator.py +144 -0
  90. code_muse/http_utils.py +360 -0
  91. code_muse/keymap.py +128 -0
  92. code_muse/list_filtering.py +26 -0
  93. code_muse/main.py +10 -0
  94. code_muse/messaging/__init__.py +259 -0
  95. code_muse/messaging/bus.py +621 -0
  96. code_muse/messaging/commands.py +166 -0
  97. code_muse/messaging/markdown_patches.py +57 -0
  98. code_muse/messaging/message_queue.py +397 -0
  99. code_muse/messaging/messages.py +591 -0
  100. code_muse/messaging/queue_console.py +269 -0
  101. code_muse/messaging/renderers.py +308 -0
  102. code_muse/messaging/rich_renderer.py +1158 -0
  103. code_muse/messaging/shimmer.py +154 -0
  104. code_muse/messaging/spinner/__init__.py +87 -0
  105. code_muse/messaging/spinner/console_spinner.py +250 -0
  106. code_muse/messaging/spinner/spinner_base.py +82 -0
  107. code_muse/messaging/subagent_console.py +458 -0
  108. code_muse/model_factory.py +1203 -0
  109. code_muse/model_switching.py +59 -0
  110. code_muse/model_utils.py +156 -0
  111. code_muse/models.json +66 -0
  112. code_muse/models_cache/__init__.py +26 -0
  113. code_muse/models_cache/blocking_lru_cache.py +98 -0
  114. code_muse/models_cache/cache_writer.py +86 -0
  115. code_muse/models_cache/sha256_hash.cpython-314-darwin.so +0 -0
  116. code_muse/models_cache/sha256_hash.pyx +34 -0
  117. code_muse/models_cache/startup_integration.py +75 -0
  118. code_muse/models_dev_api.json +1 -0
  119. code_muse/models_dev_parser.py +590 -0
  120. code_muse/motion.py +126 -0
  121. code_muse/plugins/__init__.py +471 -0
  122. code_muse/plugins/agent_skills/__init__.py +32 -0
  123. code_muse/plugins/agent_skills/config.py +176 -0
  124. code_muse/plugins/agent_skills/discovery.py +309 -0
  125. code_muse/plugins/agent_skills/downloader.py +389 -0
  126. code_muse/plugins/agent_skills/installer.py +19 -0
  127. code_muse/plugins/agent_skills/metadata.py +293 -0
  128. code_muse/plugins/agent_skills/prompt_builder.py +66 -0
  129. code_muse/plugins/agent_skills/register_callbacks.py +298 -0
  130. code_muse/plugins/agent_skills/remote_catalog.py +320 -0
  131. code_muse/plugins/agent_skills/skill_catalog.py +254 -0
  132. code_muse/plugins/agent_skills/skills_install_menu.py +690 -0
  133. code_muse/plugins/agent_skills/skills_menu.py +791 -0
  134. code_muse/plugins/autonomous_memory/__init__.py +39 -0
  135. code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-darwin.so +0 -0
  136. code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-x86_64-linux-gnu.so +0 -0
  137. code_muse/plugins/autonomous_memory/bm25_scorer.pyx +291 -0
  138. code_muse/plugins/autonomous_memory/consolidation.py +82 -0
  139. code_muse/plugins/autonomous_memory/extraction.py +382 -0
  140. code_muse/plugins/autonomous_memory/lease_lock.py +105 -0
  141. code_muse/plugins/autonomous_memory/memory_injection.py +59 -0
  142. code_muse/plugins/autonomous_memory/register_callbacks.py +268 -0
  143. code_muse/plugins/autonomous_memory/secret_scanner.py +62 -0
  144. code_muse/plugins/autonomous_memory/session_scanner.py +163 -0
  145. code_muse/plugins/aws_bedrock/__init__.py +14 -0
  146. code_muse/plugins/aws_bedrock/config.py +99 -0
  147. code_muse/plugins/aws_bedrock/register_callbacks.py +241 -0
  148. code_muse/plugins/aws_bedrock/utils.py +153 -0
  149. code_muse/plugins/azure_foundry/README.md +238 -0
  150. code_muse/plugins/azure_foundry/__init__.py +15 -0
  151. code_muse/plugins/azure_foundry/config.py +125 -0
  152. code_muse/plugins/azure_foundry/discovery.py +187 -0
  153. code_muse/plugins/azure_foundry/register_callbacks.py +495 -0
  154. code_muse/plugins/azure_foundry/token.py +180 -0
  155. code_muse/plugins/azure_foundry/utils.py +345 -0
  156. code_muse/plugins/build_filter/__init__.py +1 -0
  157. code_muse/plugins/build_filter/register_callbacks.py +201 -0
  158. code_muse/plugins/build_filter/strategies/__init__.py +1 -0
  159. code_muse/plugins/build_filter/strategies/build.py +397 -0
  160. code_muse/plugins/chatgpt_oauth/__init__.py +6 -0
  161. code_muse/plugins/chatgpt_oauth/config.py +52 -0
  162. code_muse/plugins/chatgpt_oauth/oauth_flow.py +338 -0
  163. code_muse/plugins/chatgpt_oauth/register_callbacks.py +172 -0
  164. code_muse/plugins/chatgpt_oauth/test_plugin.py +301 -0
  165. code_muse/plugins/chatgpt_oauth/utils.py +538 -0
  166. code_muse/plugins/checkpointing/__init__.py +29 -0
  167. code_muse/plugins/checkpointing/checkpoint_hook.py +51 -0
  168. code_muse/plugins/checkpointing/conversation_snapshots.py +117 -0
  169. code_muse/plugins/checkpointing/register_callbacks.py +51 -0
  170. code_muse/plugins/checkpointing/restore_command.py +263 -0
  171. code_muse/plugins/checkpointing/rewind_shortcut.py +88 -0
  172. code_muse/plugins/checkpointing/shadow_git.py +90 -0
  173. code_muse/plugins/claude_code_hooks/__init__.py +1 -0
  174. code_muse/plugins/claude_code_hooks/config.py +188 -0
  175. code_muse/plugins/claude_code_hooks/register_callbacks.py +208 -0
  176. code_muse/plugins/claude_code_oauth/README.md +167 -0
  177. code_muse/plugins/claude_code_oauth/SETUP.md +93 -0
  178. code_muse/plugins/claude_code_oauth/__init__.py +25 -0
  179. code_muse/plugins/claude_code_oauth/config.py +52 -0
  180. code_muse/plugins/claude_code_oauth/fast_mode.py +124 -0
  181. code_muse/plugins/claude_code_oauth/prompt_handler.py +63 -0
  182. code_muse/plugins/claude_code_oauth/register_callbacks.py +547 -0
  183. code_muse/plugins/claude_code_oauth/test_fast_mode.py +165 -0
  184. code_muse/plugins/claude_code_oauth/test_plugin.py +283 -0
  185. code_muse/plugins/claude_code_oauth/token_refresh_heartbeat.py +237 -0
  186. code_muse/plugins/claude_code_oauth/utils.py +664 -0
  187. code_muse/plugins/copilot_auth/__init__.py +11 -0
  188. code_muse/plugins/copilot_auth/config.py +91 -0
  189. code_muse/plugins/copilot_auth/reasoning_client.py +409 -0
  190. code_muse/plugins/copilot_auth/register_callbacks.py +461 -0
  191. code_muse/plugins/copilot_auth/utils.py +584 -0
  192. code_muse/plugins/custom_commands/__init__.py +14 -0
  193. code_muse/plugins/custom_commands/args_injection.py +82 -0
  194. code_muse/plugins/custom_commands/command_discovery.py +89 -0
  195. code_muse/plugins/custom_commands/command_toml_schema.py +71 -0
  196. code_muse/plugins/custom_commands/register_callbacks.py +176 -0
  197. code_muse/plugins/customizable_commands/__init__.py +0 -0
  198. code_muse/plugins/customizable_commands/register_callbacks.py +136 -0
  199. code_muse/plugins/destructive_command_guard/__init__.py +14 -0
  200. code_muse/plugins/destructive_command_guard/detector.py +375 -0
  201. code_muse/plugins/destructive_command_guard/register_callbacks.py +148 -0
  202. code_muse/plugins/example_custom_command/README.md +280 -0
  203. code_muse/plugins/example_custom_command/register_callbacks.py +51 -0
  204. code_muse/plugins/file_permission_handler/__init__.py +4 -0
  205. code_muse/plugins/file_permission_handler/register_callbacks.py +441 -0
  206. code_muse/plugins/filter_engine/__init__.py +30 -0
  207. code_muse/plugins/filter_engine/classifier.py +153 -0
  208. code_muse/plugins/filter_engine/content_detector.py +184 -0
  209. code_muse/plugins/filter_engine/dispatcher.py +244 -0
  210. code_muse/plugins/filter_engine/register_callbacks.py +188 -0
  211. code_muse/plugins/filter_engine/registry.py +279 -0
  212. code_muse/plugins/filter_engine/strategies/__init__.py +8 -0
  213. code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-darwin.so +0 -0
  214. code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-x86_64-linux-gnu.so +0 -0
  215. code_muse/plugins/filter_engine/strategies/ast_compressor.pyx +348 -0
  216. code_muse/plugins/filter_engine/strategies/ast_parser.py +167 -0
  217. code_muse/plugins/filter_engine/strategies/code.cpython-314-darwin.so +0 -0
  218. code_muse/plugins/filter_engine/strategies/code.cpython-314-x86_64-linux-gnu.so +0 -0
  219. code_muse/plugins/filter_engine/strategies/code.pyx +584 -0
  220. code_muse/plugins/filter_engine/strategies/git.cpython-314-darwin.so +0 -0
  221. code_muse/plugins/filter_engine/strategies/git.cpython-314-x86_64-linux-gnu.so +0 -0
  222. code_muse/plugins/filter_engine/strategies/git.pyx +438 -0
  223. code_muse/plugins/filter_engine/strategies/json_compressor.cpython-314-darwin.so +0 -0
  224. code_muse/plugins/filter_engine/strategies/json_compressor.pyx +253 -0
  225. code_muse/plugins/filter_engine/strategies/json_patterns.cpython-314-darwin.so +0 -0
  226. code_muse/plugins/filter_engine/strategies/json_patterns.pyx +178 -0
  227. code_muse/plugins/filter_engine/strategies/lint.cpython-314-darwin.so +0 -0
  228. code_muse/plugins/filter_engine/strategies/lint.cpython-314-x86_64-linux-gnu.so +0 -0
  229. code_muse/plugins/filter_engine/strategies/lint.pyx +626 -0
  230. code_muse/plugins/filter_engine/strategies/test.cpython-314-darwin.so +0 -0
  231. code_muse/plugins/filter_engine/strategies/test.cpython-314-x86_64-linux-gnu.so +0 -0
  232. code_muse/plugins/filter_engine/strategies/test.pyx +431 -0
  233. code_muse/plugins/filter_engine/verbosity.py +63 -0
  234. code_muse/plugins/force_push_guard/__init__.py +5 -0
  235. code_muse/plugins/force_push_guard/detector.py +96 -0
  236. code_muse/plugins/force_push_guard/register_callbacks.py +144 -0
  237. code_muse/plugins/force_push_guard/test_detector.py +143 -0
  238. code_muse/plugins/frontend_emitter/__init__.py +25 -0
  239. code_muse/plugins/frontend_emitter/emitter.py +121 -0
  240. code_muse/plugins/frontend_emitter/register_callbacks.py +259 -0
  241. code_muse/plugins/gac/__init__.py +4 -0
  242. code_muse/plugins/gac/git_ops.py +136 -0
  243. code_muse/plugins/gac/prompt.py +191 -0
  244. code_muse/plugins/gac/register_callbacks.py +82 -0
  245. code_muse/plugins/hook_creator/__init__.py +1 -0
  246. code_muse/plugins/hook_creator/register_callbacks.py +34 -0
  247. code_muse/plugins/hook_manager/__init__.py +1 -0
  248. code_muse/plugins/hook_manager/config.py +289 -0
  249. code_muse/plugins/hook_manager/hooks_menu.py +563 -0
  250. code_muse/plugins/hook_manager/register_callbacks.py +227 -0
  251. code_muse/plugins/hook_monitor/register_callbacks.py +36 -0
  252. code_muse/plugins/mindpack/__init__.py +0 -0
  253. code_muse/plugins/mindpack/factory.py +930 -0
  254. code_muse/plugins/mindpack/judge.py +573 -0
  255. code_muse/plugins/mindpack/memory.py +100 -0
  256. code_muse/plugins/mindpack/mindpack_menu.py +1552 -0
  257. code_muse/plugins/mindpack/orchestration.py +605 -0
  258. code_muse/plugins/mindpack/register_callbacks.py +175 -0
  259. code_muse/plugins/mindpack/schemas.py +358 -0
  260. code_muse/plugins/mindpack/tools.py +387 -0
  261. code_muse/plugins/oauth_muse_html.py +226 -0
  262. code_muse/plugins/ollama_setup/__init__.py +5 -0
  263. code_muse/plugins/ollama_setup/completer.py +36 -0
  264. code_muse/plugins/ollama_setup/register_callbacks.py +410 -0
  265. code_muse/plugins/plan_command/__init__.py +0 -0
  266. code_muse/plugins/plan_command/register_callbacks.py +206 -0
  267. code_muse/plugins/plan_mode/__init__.py +37 -0
  268. code_muse/plugins/plan_mode/mode_cycling.py +40 -0
  269. code_muse/plugins/plan_mode/plan_generation.py +68 -0
  270. code_muse/plugins/plan_mode/plan_hooks.py +74 -0
  271. code_muse/plugins/plan_mode/plan_mode_tools.py +138 -0
  272. code_muse/plugins/plan_mode/register_callbacks.py +121 -0
  273. code_muse/plugins/plugin_trust/register_callbacks.py +140 -0
  274. code_muse/plugins/policy_engine/__init__.py +46 -0
  275. code_muse/plugins/policy_engine/approval_flow_integration.py +59 -0
  276. code_muse/plugins/policy_engine/policy_evaluator.py +75 -0
  277. code_muse/plugins/policy_engine/policy_file_discovery.py +90 -0
  278. code_muse/plugins/policy_engine/policy_toml_schema.py +115 -0
  279. code_muse/plugins/policy_engine/register_callbacks.py +112 -0
  280. code_muse/plugins/pop_command/__init__.py +1 -0
  281. code_muse/plugins/pop_command/register_callbacks.py +189 -0
  282. code_muse/plugins/prompt_newline/__init__.py +13 -0
  283. code_muse/plugins/prompt_newline/config.py +19 -0
  284. code_muse/plugins/prompt_newline/register_callbacks.py +159 -0
  285. code_muse/plugins/safety_status/__init__.py +0 -0
  286. code_muse/plugins/safety_status/register_callbacks.py +113 -0
  287. code_muse/plugins/semantic_compression/__init__.py +6 -0
  288. code_muse/plugins/semantic_compression/compressor.py +295 -0
  289. code_muse/plugins/semantic_compression/config.py +123 -0
  290. code_muse/plugins/semantic_compression/register_callbacks.py +320 -0
  291. code_muse/plugins/shell_minimizer/__init__.py +50 -0
  292. code_muse/plugins/shell_minimizer/builtin_filters.toml +393 -0
  293. code_muse/plugins/shell_minimizer/pipeline.py +556 -0
  294. code_muse/plugins/shell_minimizer/primitives.py +482 -0
  295. code_muse/plugins/shell_minimizer/register_callbacks.py +276 -0
  296. code_muse/plugins/shell_safety/__init__.py +6 -0
  297. code_muse/plugins/shell_safety/agent_shell_safety.py +69 -0
  298. code_muse/plugins/shell_safety/command_cache.py +149 -0
  299. code_muse/plugins/shell_safety/register_callbacks.py +202 -0
  300. code_muse/plugins/synthetic_status/__init__.py +1 -0
  301. code_muse/plugins/synthetic_status/register_callbacks.py +128 -0
  302. code_muse/plugins/synthetic_status/status_api.py +145 -0
  303. code_muse/plugins/token_caching/__init__.py +21 -0
  304. code_muse/plugins/token_caching/cache_hit_tracking.py +128 -0
  305. code_muse/plugins/token_caching/cacheable_prefix_detection.py +28 -0
  306. code_muse/plugins/token_caching/register_callbacks.py +54 -0
  307. code_muse/plugins/token_caching/stats_display.py +35 -0
  308. code_muse/plugins/token_tracking/__init__.py +26 -0
  309. code_muse/plugins/token_tracking/database.py +381 -0
  310. code_muse/plugins/token_tracking/edit_analyzer.py +97 -0
  311. code_muse/plugins/token_tracking/record.py +55 -0
  312. code_muse/plugins/token_tracking/register_callbacks.py +277 -0
  313. code_muse/plugins/token_tracking/reports.py +329 -0
  314. code_muse/plugins/universal_constructor/__init__.py +13 -0
  315. code_muse/plugins/universal_constructor/models.py +136 -0
  316. code_muse/plugins/universal_constructor/register_callbacks.py +47 -0
  317. code_muse/plugins/universal_constructor/registry.py +390 -0
  318. code_muse/plugins/universal_constructor/runner.py +474 -0
  319. code_muse/plugins/universal_constructor/safety.py +440 -0
  320. code_muse/plugins/universal_constructor/sandbox.py +584 -0
  321. code_muse/provider_identity.py +105 -0
  322. code_muse/pydantic_patches.py +410 -0
  323. code_muse/reopenable_async_client.py +233 -0
  324. code_muse/round_robin_model.py +151 -0
  325. code_muse/secret_storage.py +74 -0
  326. code_muse/security/__init__.py +1 -0
  327. code_muse/security/redaction.cpython-314-darwin.so +0 -0
  328. code_muse/security/redaction.cpython-314-x86_64-linux-gnu.so +0 -0
  329. code_muse/security/redaction.pyx +135 -0
  330. code_muse/session_storage.py +565 -0
  331. code_muse/status_display.py +261 -0
  332. code_muse/stream_parser/__init__.py +76 -0
  333. code_muse/stream_parser/assistant_text_parser.py +90 -0
  334. code_muse/stream_parser/citation_parser.py +76 -0
  335. code_muse/stream_parser/inline_hidden_tag_parser.py +236 -0
  336. code_muse/stream_parser/proposed_plan_parser.py +158 -0
  337. code_muse/stream_parser/stream_text_chunk.py +23 -0
  338. code_muse/stream_parser/stream_text_parser.py +27 -0
  339. code_muse/stream_parser/tagged_line_parser.cpython-314-darwin.so +0 -0
  340. code_muse/stream_parser/tagged_line_parser.pyx +251 -0
  341. code_muse/stream_parser/utf8_stream_parser.cpython-314-darwin.so +0 -0
  342. code_muse/stream_parser/utf8_stream_parser.pyx +206 -0
  343. code_muse/summarization_agent.py +308 -0
  344. code_muse/terminal_utils.cpython-314-darwin.so +0 -0
  345. code_muse/terminal_utils.cpython-314-x86_64-linux-gnu.so +0 -0
  346. code_muse/terminal_utils.pyx +483 -0
  347. code_muse/tools/__init__.py +459 -0
  348. code_muse/tools/agent_tools.py +613 -0
  349. code_muse/tools/ask_user_question/__init__.py +26 -0
  350. code_muse/tools/ask_user_question/constants.py +73 -0
  351. code_muse/tools/ask_user_question/demo_tui.py +55 -0
  352. code_muse/tools/ask_user_question/handler.py +232 -0
  353. code_muse/tools/ask_user_question/models.py +302 -0
  354. code_muse/tools/ask_user_question/registration.py +37 -0
  355. code_muse/tools/ask_user_question/renderers.py +336 -0
  356. code_muse/tools/ask_user_question/terminal_ui.py +327 -0
  357. code_muse/tools/ask_user_question/theme.py +156 -0
  358. code_muse/tools/ask_user_question/tui_loop.py +422 -0
  359. code_muse/tools/background_jobs.py +99 -0
  360. code_muse/tools/browser/__init__.py +37 -0
  361. code_muse/tools/browser/browser_control.py +289 -0
  362. code_muse/tools/browser/browser_interactions.py +545 -0
  363. code_muse/tools/browser/browser_locators.py +640 -0
  364. code_muse/tools/browser/browser_manager.py +376 -0
  365. code_muse/tools/browser/browser_navigation.py +251 -0
  366. code_muse/tools/browser/browser_screenshot.py +180 -0
  367. code_muse/tools/browser/browser_scripts.py +462 -0
  368. code_muse/tools/browser/browser_workflows.py +222 -0
  369. code_muse/tools/chrome_cdp/__init__.py +1070 -0
  370. code_muse/tools/chrome_cdp/register_callbacks.py +61 -0
  371. code_muse/tools/command_runner.py +1401 -0
  372. code_muse/tools/common.py +1407 -0
  373. code_muse/tools/display.py +87 -0
  374. code_muse/tools/file_modifications.py +1099 -0
  375. code_muse/tools/file_operations.py +860 -0
  376. code_muse/tools/image_tools.py +185 -0
  377. code_muse/tools/meetin_proxy/__init__.py +243 -0
  378. code_muse/tools/meetin_proxy/capture_addon.py +82 -0
  379. code_muse/tools/meetin_proxy/proxy_manager.py +326 -0
  380. code_muse/tools/meetin_proxy/register_callbacks.py +45 -0
  381. code_muse/tools/path_policy.py +219 -0
  382. code_muse/tools/skills_tools.py +586 -0
  383. code_muse/tools/subagent_context.py +158 -0
  384. code_muse/tools/tools_content.py +50 -0
  385. code_muse/tools/universal_constructor.py +965 -0
  386. code_muse/uvx_detection.py +241 -0
  387. code_muse/version_checker.py +86 -0
  388. code_muse-0.0.1.data/data/code_muse/models.json +66 -0
  389. code_muse-0.0.1.data/data/code_muse/models_dev_api.json +1 -0
  390. code_muse-0.0.1.dist-info/METADATA +845 -0
  391. code_muse-0.0.1.dist-info/RECORD +394 -0
  392. code_muse-0.0.1.dist-info/WHEEL +4 -0
  393. code_muse-0.0.1.dist-info/entry_points.txt +2 -0
  394. code_muse-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,573 @@
1
+ """MindPack judge — LLM-backed report merging and AskMindPackOutput synthesis.
2
+
3
+ The judge reviews all expert reports, evaluates consensus and disagreement,
4
+ and produces the final unified advisory output.
5
+
6
+ Two components:
7
+
8
+ 1. ``JudgeAgentFactory`` — creates a read-only "Judge" sub-agent that
9
+ produces structured ``AskMindPackOutput`` from expert reports.
10
+ 2. ``LLMJudgeMerger`` — concrete ``JudgeMerger`` that delegates to the
11
+ factory, with a built-in fallback to placeholder logic on failure.
12
+
13
+ Both the judge agent and the merger degrade gracefully — the executor
14
+ always receives a valid ``AskMindPackOutput``, even if the LLM is down.
15
+ """
16
+
17
+ import asyncio
18
+ import logging
19
+ import uuid
20
+ from contextlib import AsyncExitStack
21
+ from functools import partial
22
+ from typing import Any
23
+
24
+ from pydantic_ai import Agent as PydanticAgent
25
+ from pydantic_ai import UsageLimits
26
+
27
+ from code_muse.plugins.mindpack.orchestration import JudgeMerger
28
+ from code_muse.plugins.mindpack.schemas import (
29
+ AskMindPackInput,
30
+ AskMindPackOutput,
31
+ MindPackRankedOption,
32
+ )
33
+ from code_muse.plugins.mindpack.schemas import (
34
+ MindPackExpertReport as ExpertReport,
35
+ )
36
+ from code_muse.tools.subagent_context import subagent_context
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Read-only tool allow-list (same as experts — judge output is advisory)
42
+ # ---------------------------------------------------------------------------
43
+
44
+ JUDGE_READ_ONLY_TOOLS: list[str] = [
45
+ "list_files",
46
+ "read_file",
47
+ "grep",
48
+ "load_image_for_analysis",
49
+ "list_or_search_skills",
50
+ ]
51
+ """Tools the judge may use. All write-capable tools are deliberately excluded
52
+ to guarantee the judge never mutates the codebase."""
53
+
54
+ # ---------------------------------------------------------------------------
55
+ # Judge system prompt
56
+ # ---------------------------------------------------------------------------
57
+
58
+ _JUDGE_SYSTEM_PROMPT = """\
59
+ You are Judge, the deliberation synthesizer for the MindPack expert panel.
60
+
61
+ Your role is to review reports from multiple domain experts, identify areas
62
+ of consensus and disagreement, and produce a unified, actionable advisory
63
+ output that the executor can rely on.
64
+
65
+ ## CRITICAL CONSTRAINTS
66
+
67
+ You are operating in **read-only advisory mode**. You must NEVER:
68
+ - Create, modify, or delete any files
69
+ - Run shell commands
70
+ - Invoke other agents or sub-agents
71
+ - Ask the user questions
72
+ - Navigate websites or use browser tools
73
+
74
+ You MAY: list/read files, search with grep, load images, list skills.
75
+
76
+ ## YOUR TASK
77
+
78
+ You will receive:
79
+ 1. The original consultation request (problem, goals, context)
80
+ 2. Structured reports from experts, each with analysis, recommendations,
81
+ risks, files, confidence, and disagreements.
82
+
83
+ You must:
84
+ 1. **Identify consensus** — What do most or all experts agree on?
85
+ 2. **Surface disagreements** — Where do experts diverge, and why?
86
+ 3. **Synthesize recommendations** — Merge overlapping ones; resolve conflicts
87
+ by weighing expert confidence and domain relevance; prefer simpler paths.
88
+ 4. **Rank options** — Up to 3 ranked alternatives. Rank 1 = most recommended.
89
+ 5. **Assess overall confidence** — Weighted average penalised for disagreements.
90
+ 6. **Flag risks** — Consolidate, deduplicate, prioritise by severity.
91
+ 7. **Recommend tests** — Concrete validation steps from the combined advice.
92
+
93
+ ## SYNTHESIS GUIDELINES
94
+
95
+ - When experts agree, state consensus clearly and move on.
96
+ - When experts disagree, present both views in ``disagreements`` and pick
97
+ the position supported by stronger reasoning or higher confidence.
98
+ - Never fabricate information not present in the expert reports.
99
+ - If an expert flagged a disagreement, explain both positions and justify
100
+ the judge's resolution.
101
+ - Overall confidence should decrease when key experts disagree on core points.
102
+ - If only one expert was consulted, still produce a full output — but note
103
+ the lack of cross-validation.
104
+
105
+ ## OUTPUT FORMAT
106
+
107
+ You MUST produce a structured ``AskMindPackOutput`` with every field filled.
108
+ """
109
+
110
+ # ---------------------------------------------------------------------------
111
+ # Prompt builder
112
+ # ---------------------------------------------------------------------------
113
+
114
+
115
+ def build_judge_prompt(
116
+ request: AskMindPackInput,
117
+ reports: list[ExpertReport],
118
+ ) -> str:
119
+ """Compose the user-facing prompt for the judge agent.
120
+
121
+ Serialises the original request and expert reports into a structured
122
+ format the judge can evaluate.
123
+ """
124
+ parts: list[str] = []
125
+
126
+ # --- Original request ---
127
+ parts.append("## ORIGINAL CONSULTATION REQUEST")
128
+ parts.append(f"**Problem Statement:** {request.problem_statement}")
129
+ parts.append(f"**Current Goal:** {request.current_goal}")
130
+ if request.current_plan:
131
+ parts.append(f"**Current Plan:** {request.current_plan}")
132
+ if request.what_has_been_tried:
133
+ parts.append(
134
+ "**What Has Been Tried:**\n"
135
+ + "\n".join(f"- {item}" for item in request.what_has_been_tried)
136
+ )
137
+ if request.relevant_files:
138
+ parts.append(
139
+ "**Relevant Files:**\n"
140
+ + "\n".join(f"- {f}" for f in request.relevant_files)
141
+ )
142
+ if request.observed_errors:
143
+ parts.append(
144
+ "**Observed Errors:**\n"
145
+ + "\n".join(f"- {e}" for e in request.observed_errors)
146
+ )
147
+ if request.uncertainty:
148
+ parts.append(f"**Uncertainty:** {request.uncertainty}")
149
+ parts.append(f"**Desired Output:** {request.desired_output}")
150
+
151
+ # --- Expert reports ---
152
+ parts.append(f"\n## EXPERT REPORTS ({len(reports)} total)")
153
+ for i, report in enumerate(reports, 1):
154
+ parts.append(f"\n### Report {i}: {report.expert_id}")
155
+ parts.append(f"**Confidence:** {report.confidence:.2f}")
156
+ parts.append(f"**Status:** {report.status}")
157
+ parts.append(f"\n**Summary:**\n{report.summary}")
158
+ if report.findings:
159
+ parts.append(
160
+ "**Findings:**\n" + "\n".join(f"- {f}" for f in report.findings)
161
+ )
162
+ if report.proposed_plan:
163
+ parts.append(
164
+ "**Proposed Plan:**\n"
165
+ + "\n".join(f"- {p}" for p in report.proposed_plan)
166
+ )
167
+ if report.risks:
168
+ parts.append("**Risks:**\n" + "\n".join(f"- {r}" for r in report.risks))
169
+ if report.files_to_inspect:
170
+ parts.append(
171
+ "**Files to Inspect:**\n"
172
+ + "\n".join(f"- {f}" for f in report.files_to_inspect)
173
+ )
174
+
175
+ return "\n\n".join(parts)
176
+
177
+
178
+ # ---------------------------------------------------------------------------
179
+ # JudgeAgentFactory
180
+ # ---------------------------------------------------------------------------
181
+
182
+
183
+ class JudgeAgentFactory:
184
+ """Creates and runs a read-only Judge sub-agent that merges expert reports.
185
+
186
+ Follows the same construction pattern as ``ExpertAgentFactory`` but
187
+ outputs ``AskMindPackOutput`` instead of ``ExpertReport``.
188
+
189
+ Usage::
190
+
191
+ factory = JudgeAgentFactory()
192
+ output = await factory.invoke_judge(request, reports, session_id)
193
+ """
194
+
195
+ def create_judge_agent(
196
+ self,
197
+ *,
198
+ session_id: str,
199
+ message_group: str | None = None,
200
+ ) -> PydanticAgent:
201
+ """Build a pydantic-ai agent for the judge.
202
+
203
+ Configured with the judge system prompt, read-only tools, and
204
+ ``AskMindPackOutput`` as the output type. This is a temporary agent
205
+ — not registered in the agent manager, does not persist.
206
+ """
207
+ from code_muse.agents._builder import load_muse_rules
208
+ from code_muse.model_factory import ModelFactory, make_model_settings
209
+ from code_muse.model_utils import prepare_prompt_for_model
210
+
211
+ # Resolve model
212
+ model_name = self._resolve_model_name()
213
+ models_config = ModelFactory.load_config()
214
+
215
+ if model_name not in models_config:
216
+ raise ValueError(
217
+ f"Model '{model_name}' not found in configuration — "
218
+ "cannot create judge agent"
219
+ )
220
+
221
+ model = ModelFactory.get_model(model_name, models_config)
222
+
223
+ # Build instructions
224
+ instructions = _JUDGE_SYSTEM_PROMPT
225
+
226
+ # Append AGENTS.md rules if available
227
+ agent_rules = load_muse_rules()
228
+ if agent_rules:
229
+ instructions += f"\n\n{agent_rules}"
230
+
231
+ # Append plugin prompt additions
232
+ from code_muse import callbacks
233
+
234
+ prompt_additions = callbacks.on_load_prompt()
235
+ if prompt_additions:
236
+ instructions += "\n" + "\n".join(prompt_additions)
237
+
238
+ # Prepare for model (handles claude-code prepending etc.)
239
+ prepared = prepare_prompt_for_model(
240
+ model_name,
241
+ instructions,
242
+ "",
243
+ prepend_system_to_user=False,
244
+ )
245
+ instructions = prepared.instructions
246
+
247
+ model_settings = make_model_settings(model_name)
248
+
249
+ # Build the pydantic-ai agent with AskMindPackOutput output type
250
+ judge_agent = PydanticAgent(
251
+ model=model,
252
+ instructions=instructions,
253
+ output_type=AskMindPackOutput,
254
+ retries=2,
255
+ toolsets=[],
256
+ history_processors=[],
257
+ model_settings=model_settings,
258
+ )
259
+
260
+ # Register read-only tools
261
+ from code_muse.tools import register_tools_for_agent
262
+
263
+ tools = list(JUDGE_READ_ONLY_TOOLS)
264
+ register_tools_for_agent(judge_agent, tools, model_name=model_name)
265
+
266
+ logger.debug(
267
+ "JudgeAgentFactory: created judge agent with tools=%s session=%s",
268
+ tools,
269
+ session_id,
270
+ )
271
+ return judge_agent
272
+
273
+ async def invoke_judge(
274
+ self,
275
+ request: AskMindPackInput,
276
+ reports: list[ExpertReport],
277
+ session_id: str,
278
+ ) -> AskMindPackOutput:
279
+ """Run the judge agent and return the merged advisory output.
280
+
281
+ Falls back to placeholder output if the judge fails entirely.
282
+ """
283
+ group_id = f"mindpack-judge-{uuid.uuid4().hex[:6]}"
284
+
285
+ try:
286
+ judge_agent = self.create_judge_agent(
287
+ session_id=session_id, message_group=group_id
288
+ )
289
+ except ValueError as exc:
290
+ logger.error("JudgeAgentFactory: failed to create judge agent: %s", exc)
291
+ return self._placeholder_output(request, reports)
292
+
293
+ user_prompt = build_judge_prompt(request, reports)
294
+
295
+ return await self._run_judge(
296
+ judge_agent=judge_agent,
297
+ user_prompt=user_prompt,
298
+ session_id=session_id,
299
+ group_id=group_id,
300
+ request=request,
301
+ reports=reports,
302
+ )
303
+
304
+ # -- internal -----------------------------------------------------------
305
+
306
+ @staticmethod
307
+ def _resolve_model_name(model_name: str | None = None) -> str:
308
+ """Resolve the model name for the judge agent.
309
+
310
+ Resolution order:
311
+ 1. Explicit ``model_name`` override (caller-supplied, e.g. from a
312
+ future registry or schema-driven config).
313
+ 2. ``packmind_judge_model`` key in the Muse INI config.
314
+ 3. Global model name from ``get_global_model_name()``.
315
+
316
+ Raises ``ValueError`` if no model can be resolved.
317
+ """
318
+ # 1. Explicit caller override
319
+ if model_name:
320
+ return model_name
321
+
322
+ # 2. Config-file override (packmind_judge_model in muse.cfg)
323
+ from code_muse.config import get_value
324
+
325
+ judge_model = get_value("packmind_judge_model")
326
+ if judge_model:
327
+ return judge_model
328
+
329
+ # 3. Global fallback
330
+ from code_muse.config import get_global_model_name
331
+
332
+ name = get_global_model_name()
333
+ if not name:
334
+ raise ValueError("No global model configured — cannot create judge agent")
335
+ return name
336
+
337
+ async def _run_judge(
338
+ self,
339
+ judge_agent: PydanticAgent,
340
+ user_prompt: str,
341
+ session_id: str,
342
+ group_id: str,
343
+ request: AskMindPackInput,
344
+ reports: list[ExpertReport],
345
+ ) -> AskMindPackOutput:
346
+ """Execute the judge agent in a subagent context."""
347
+ from code_muse.agents.subagent_stream_handler import (
348
+ subagent_stream_handler,
349
+ )
350
+ from code_muse.callbacks import on_agent_run_cancel, on_agent_run_context
351
+ from code_muse.config import get_message_limit
352
+ from code_muse.messaging import (
353
+ SubAgentInvocationMessage,
354
+ SubAgentResponseMessage,
355
+ emit_success,
356
+ get_message_bus,
357
+ )
358
+
359
+ bus = get_message_bus()
360
+
361
+ bus.emit(
362
+ SubAgentInvocationMessage(
363
+ agent_name="mindpack-judge",
364
+ session_id=session_id,
365
+ prompt=user_prompt[:200],
366
+ is_new_session=True,
367
+ message_count=0,
368
+ )
369
+ )
370
+
371
+ stream_handler = partial(subagent_stream_handler, session_id=session_id)
372
+
373
+ with subagent_context("mindpack-judge"):
374
+ run_ctxs = on_agent_run_context(
375
+ _JudgeAgentProxy(),
376
+ judge_agent,
377
+ group_id,
378
+ )
379
+
380
+ task: asyncio.Task | None = None
381
+ try:
382
+ async with AsyncExitStack() as stack:
383
+ for cm in run_ctxs:
384
+ await stack.enter_async_context(cm)
385
+
386
+ task = asyncio.create_task(
387
+ judge_agent.run(
388
+ user_prompt,
389
+ message_history=[],
390
+ usage_limits=UsageLimits(request_limit=get_message_limit()),
391
+ event_stream_handler=stream_handler,
392
+ )
393
+ )
394
+
395
+ result = await task
396
+
397
+ except asyncio.CancelledError:
398
+ if task and not task.done():
399
+ task.cancel()
400
+ await on_agent_run_cancel(group_id)
401
+ logger.warning("JudgeAgentFactory: judge cancelled")
402
+ return self._placeholder_output(request, reports)
403
+
404
+ except Exception as exc:
405
+ logger.error(
406
+ "JudgeAgentFactory: judge failed: %s",
407
+ exc,
408
+ exc_info=True,
409
+ )
410
+ return self._placeholder_output(request, reports)
411
+
412
+ # Extract structured output
413
+ output = self._extract_output(result, request, reports)
414
+
415
+ # Emit completion message
416
+ bus.emit(
417
+ SubAgentResponseMessage(
418
+ agent_name="mindpack-judge",
419
+ session_id=session_id,
420
+ response=output.summary[:200] if output else "",
421
+ message_count=0,
422
+ )
423
+ )
424
+
425
+ emit_success(
426
+ "✓ mindpack-judge completed",
427
+ message_group=group_id,
428
+ )
429
+
430
+ return output
431
+
432
+ @staticmethod
433
+ def _extract_output(
434
+ result: Any,
435
+ request: AskMindPackInput,
436
+ reports: list[ExpertReport],
437
+ ) -> AskMindPackOutput:
438
+ """Extract ``AskMindPackOutput`` from the pydantic-ai result.
439
+
440
+ Tries structured output, then dict coercion, then placeholder.
441
+ """
442
+ if result is not None and hasattr(result, "output"):
443
+ output = result.output
444
+ if isinstance(output, AskMindPackOutput):
445
+ return output
446
+
447
+ # Dict path — model returned raw dict instead of model instance
448
+ if isinstance(output, dict):
449
+ try:
450
+ return AskMindPackOutput(
451
+ **{
452
+ k: v
453
+ for k, v in output.items()
454
+ if k in AskMindPackOutput.model_fields
455
+ }
456
+ )
457
+ except Exception:
458
+ pass
459
+
460
+ # Final fallback
461
+ return JudgeAgentFactory._placeholder_output(request, reports)
462
+
463
+ @staticmethod
464
+ def _placeholder_output(
465
+ request: AskMindPackInput,
466
+ reports: list[ExpertReport],
467
+ ) -> AskMindPackOutput:
468
+ """Build a placeholder output when the judge cannot produce one.
469
+
470
+ Mirrors ``PlaceholderJudgeMerger`` for graceful degradation.
471
+ """
472
+ expert_names = [r.expert_id for r in reports]
473
+ all_risks = [r for report in reports for r in report.risks]
474
+ all_files = [f for report in reports for f in report.files_to_inspect]
475
+ all_recs = [r for report in reports for r in report.proposed_plan]
476
+
477
+ return AskMindPackOutput(
478
+ summary=(
479
+ f"Judge fallback: consulted {len(reports)} expert(s) "
480
+ f"for '{request.desired_output}' on: "
481
+ f"{request.problem_statement[:200]}"
482
+ ),
483
+ recommended_plan=("\n".join(all_recs) or "No recommendations produced."),
484
+ ranked_options=[
485
+ MindPackRankedOption(
486
+ rank=1,
487
+ title="Fallback option",
488
+ source_experts=expert_names,
489
+ summary=("Judge LLM failed — merged option from raw expert data."),
490
+ )
491
+ ],
492
+ risks=all_risks or ["No risks identified (judge fallback)."],
493
+ tests_to_run=[],
494
+ files_to_inspect_or_change=list(dict.fromkeys(all_files)),
495
+ expert_consensus=(f"Fallback: {len(reports)} expert(s) consulted."),
496
+ disagreements=[],
497
+ confidence=(sum(r.confidence for r in reports) / max(len(reports), 1)),
498
+ )
499
+
500
+
501
+ # ---------------------------------------------------------------------------
502
+ # Minimal agent proxy for callback hooks
503
+ # ---------------------------------------------------------------------------
504
+
505
+
506
+ class _JudgeAgentProxy:
507
+ """Lightweight proxy for ``on_agent_run_context`` callback hooks."""
508
+
509
+ def __init__(self) -> None:
510
+ self.name = "mindpack-judge"
511
+ self._model_name: str | None = None
512
+
513
+ def get_model_name(self) -> str:
514
+ if self._model_name is None:
515
+ self._model_name = JudgeAgentFactory._resolve_model_name() or "unknown"
516
+ return self._model_name
517
+
518
+
519
+ # ---------------------------------------------------------------------------
520
+ # LLMJudgeMerger
521
+ # ---------------------------------------------------------------------------
522
+
523
+
524
+ class LLMJudgeMerger(JudgeMerger):
525
+ """Concrete ``JudgeMerger`` that delegates merging to an LLM judge.
526
+
527
+ Falls back to placeholder-style merging if the judge fails, so the
528
+ orchestrator always receives a valid ``AskMindPackOutput``.
529
+ """
530
+
531
+ def __init__(
532
+ self,
533
+ factory: JudgeAgentFactory | None = None,
534
+ ) -> None:
535
+ self._factory = factory or JudgeAgentFactory()
536
+
537
+ async def merge(
538
+ self,
539
+ request: AskMindPackInput,
540
+ reports: list[ExpertReport],
541
+ session_id: str,
542
+ ) -> AskMindPackOutput:
543
+ """Produce a unified advisory from all expert reports via LLM judge.
544
+
545
+ Degrades to placeholder merge if the judge fails.
546
+ """
547
+ if not reports:
548
+ return self._empty_output(request)
549
+
550
+ try:
551
+ return await self._factory.invoke_judge(request, reports, session_id)
552
+ except Exception as exc:
553
+ logger.error(
554
+ "LLMJudgeMerger: judge invocation failed, using fallback: %s",
555
+ exc,
556
+ exc_info=True,
557
+ )
558
+ return JudgeAgentFactory._placeholder_output(request, reports)
559
+
560
+ @staticmethod
561
+ def _empty_output(request: AskMindPackInput) -> AskMindPackOutput:
562
+ """Minimal output when no expert reports are available."""
563
+ return AskMindPackOutput(
564
+ summary=(f"No expert reports available for '{request.desired_output}'."),
565
+ recommended_plan=("Unable to produce a plan — no experts consulted."),
566
+ ranked_options=[],
567
+ risks=["No experts were consulted — advice is unvalidated."],
568
+ tests_to_run=[],
569
+ files_to_inspect_or_change=list(request.relevant_files),
570
+ expert_consensus="N/A — no reports received.",
571
+ disagreements=[],
572
+ confidence=0.0,
573
+ )
@@ -0,0 +1,100 @@
1
+ """MindPack memory — ReportStore for expert report buffering.
2
+
3
+ Provides a structured, flexible storage system for expert reports.
4
+ Supports in-memory buffering, persistent caching, and workspace-local storage.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import pathlib
10
+
11
+ from pydantic import BaseModel
12
+
13
+ from code_muse.plugins.mindpack.schemas import (
14
+ MindPackExpertReport,
15
+ MindPackMergedDecision,
16
+ MindPackReportStoreConfig,
17
+ ReportStoreMode,
18
+ )
19
+ from code_muse.security.redaction import redact_secrets
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class ReportStore:
25
+ """Manages storage for MindPack expert reports and final decisions."""
26
+
27
+ def __init__(self, config: MindPackReportStoreConfig | None = None) -> None:
28
+ self.config = config or MindPackReportStoreConfig()
29
+ self._memory_store: dict[str, list[MindPackExpertReport]] = {}
30
+ self._decisions: dict[str, MindPackMergedDecision] = {}
31
+
32
+ def _get_storage_path(
33
+ self, run_id: str, mode: ReportStoreMode
34
+ ) -> pathlib.Path | None:
35
+ if mode == "cache":
36
+ path = pathlib.Path(self.config.cache_dir).expanduser() / run_id
37
+ path.mkdir(parents=True, exist_ok=True)
38
+ return path
39
+ elif mode == "workspace":
40
+ path = pathlib.Path.cwd() / self.config.workspace_dir / run_id
41
+ path.mkdir(parents=True, exist_ok=True)
42
+ return path
43
+ return None
44
+
45
+ def _save_to_disk(self, run_id: str, data: BaseModel, filename: str) -> None:
46
+ """Helper to save serializable data to disk."""
47
+ modes = (
48
+ [self.config.mode] if self.config.mode != "both" else ["cache", "workspace"]
49
+ )
50
+ for mode in modes:
51
+ path = self._get_storage_path(run_id, mode) # type: ignore
52
+ if path:
53
+ file_path = path / filename
54
+ data_dict = data.model_dump()
55
+ if not self.config.save_raw_transcripts:
56
+ data_dict = redact_secrets(data_dict)
57
+ with open(file_path, "w") as f:
58
+ json.dump(data_dict, f, indent=2)
59
+ logger.debug("ReportStore: saved %s to %s", filename, file_path)
60
+
61
+ def add_report(self, report: MindPackExpertReport) -> None:
62
+ """Stores a report in memory and optionally persists it."""
63
+ if self.config.mode in ["memory", "both"]:
64
+ if report.run_id not in self._memory_store:
65
+ self._memory_store[report.run_id] = []
66
+ self._memory_store[report.run_id].append(report)
67
+
68
+ if self.config.mode in ["cache", "workspace", "both"]:
69
+ self._save_to_disk(report.run_id, report, f"report_{report.expert_id}.json")
70
+
71
+ def save_reports(self, reports: list[MindPackExpertReport]) -> None:
72
+ """Batch save reports."""
73
+ for report in reports:
74
+ self.add_report(report)
75
+
76
+ def save_merged_decision(self, merged: MindPackMergedDecision) -> None:
77
+ """Stores and persists the final judge decision."""
78
+ self._decisions[merged.run_id] = merged
79
+ if self.config.mode in ["cache", "workspace", "both"]:
80
+ self._save_to_disk(merged.run_id, merged, "decision.json")
81
+
82
+ def get_reports(self, run_id: str) -> list[MindPackExpertReport]:
83
+ """Fetch reports from memory."""
84
+ return self._memory_store.get(run_id, [])
85
+
86
+ def clear_run(self, run_id: str) -> None:
87
+ """Clears memory for a run."""
88
+ self._memory_store.pop(run_id, None)
89
+ self._decisions.pop(run_id, None)
90
+ logger.debug("ReportStore: cleared run '%s'", run_id)
91
+
92
+ def report_count(self, run_id: str) -> int:
93
+ """Return the number of reports buffered for a run."""
94
+ return len(self._memory_store.get(run_id, []))
95
+
96
+ def clear_all(self) -> None:
97
+ """Clears all buffered memory and decisions."""
98
+ self._memory_store.clear()
99
+ self._decisions.clear()
100
+ logger.debug("ReportStore: cleared all runs")