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,117 @@
1
+ """Conversation snapshot serialization for checkpointing."""
2
+
3
+ import json
4
+ import logging
5
+ from datetime import UTC, datetime
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from code_muse.agents import get_current_agent
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def create_snapshot(
15
+ agent: Any, tool_name: str, tool_call_id: str | None = None
16
+ ) -> Path | None:
17
+ """Serialize the current conversation state to a JSON snapshot."""
18
+ try:
19
+ if agent is None:
20
+ agent = get_current_agent()
21
+
22
+ messages = list(agent.get_message_history())
23
+ timestamp = datetime.now(UTC).isoformat()
24
+
25
+ snapshot: dict[str, Any] = {
26
+ "turn_id": f"{tool_name}_{timestamp}",
27
+ "timestamp": timestamp,
28
+ "tool_name": tool_name,
29
+ "tool_call_id": tool_call_id or "",
30
+ "messages": _serialize_messages(messages),
31
+ "agent_state": {
32
+ "name": getattr(agent, "name", "unknown"),
33
+ "model": getattr(agent, "model", "unknown"),
34
+ },
35
+ }
36
+
37
+ # Store in shadow git repo area
38
+ project_root = Path.cwd()
39
+ project_hash = _hash_project_root(str(project_root))
40
+ repo_path = Path.home() / ".muse" / "history" / project_hash
41
+ snapshot_dir = repo_path / "snapshots"
42
+ snapshot_dir.mkdir(parents=True, exist_ok=True)
43
+
44
+ snapshot_path = snapshot_dir / f"snapshot_{timestamp.replace(':', '_')}.json"
45
+ snapshot_path.write_text(json.dumps(snapshot, indent=2), encoding="utf-8")
46
+
47
+ logger.info(f"Conversation snapshot saved to {snapshot_path}")
48
+ return snapshot_path
49
+ except Exception as exc:
50
+ logger.error(f"Failed to create snapshot: {exc}")
51
+ return None
52
+
53
+
54
+ def load_snapshot(path: Path) -> dict[str, Any] | None:
55
+ """Load and validate a snapshot JSON file."""
56
+ try:
57
+ data = json.loads(path.read_text(encoding="utf-8"))
58
+ required_keys = {"turn_id", "timestamp", "tool_name", "messages", "agent_state"}
59
+ if not required_keys.issubset(data.keys()):
60
+ logger.warning(f"Snapshot at {path} is missing required keys")
61
+ return None
62
+ return data
63
+ except Exception as exc:
64
+ logger.error(f"Failed to load snapshot {path}: {exc}")
65
+ return None
66
+
67
+
68
+ def list_snapshots(repo_path: Path) -> list[dict[str, Any]]:
69
+ """Scan snapshot directory and return metadata sorted newest-first."""
70
+ snapshot_dir = repo_path / "snapshots"
71
+ if not snapshot_dir.exists():
72
+ return []
73
+
74
+ results: list[dict[str, Any]] = []
75
+ for path in snapshot_dir.glob("snapshot_*.json"):
76
+ data = load_snapshot(path)
77
+ if data is None:
78
+ continue
79
+ results.append(
80
+ {
81
+ "timestamp": data.get("timestamp", ""),
82
+ "tool_name": data.get("tool_name", ""),
83
+ "tool_call_id": data.get("tool_call_id", ""),
84
+ "path": str(path),
85
+ }
86
+ )
87
+
88
+ results.sort(key=lambda x: x["timestamp"], reverse=True)
89
+ return results
90
+
91
+
92
+ def _hash_project_root(project_root: str) -> str:
93
+ import hashlib
94
+
95
+ return hashlib.sha256(project_root.encode()).hexdigest()
96
+
97
+
98
+ def _serialize_messages(messages: list[Any]) -> list[dict[str, Any]]:
99
+ """Best-effort serialization of message history to JSON-safe dicts."""
100
+ serialized: list[dict[str, Any]] = []
101
+ for msg in messages:
102
+ try:
103
+ if hasattr(msg, "model_dump"):
104
+ serialized.append(msg.model_dump())
105
+ elif hasattr(msg, "dict"):
106
+ serialized.append(msg.dict())
107
+ else:
108
+ serialized.append(
109
+ {
110
+ "type": type(msg).__name__,
111
+ "repr": repr(msg),
112
+ }
113
+ )
114
+ except Exception as exc:
115
+ logger.warning(f"Could not serialize message: {exc}")
116
+ serialized.append({"type": "unserializable", "error": str(exc)})
117
+ return serialized
@@ -0,0 +1,51 @@
1
+ """Register checkpointing callbacks and commands."""
2
+
3
+ import logging
4
+
5
+ from code_muse.callbacks import register_callback
6
+ from code_muse.plugins.checkpointing.checkpoint_hook import (
7
+ on_pre_tool_call_checkpoint,
8
+ )
9
+ from code_muse.plugins.checkpointing.restore_command import (
10
+ _custom_help,
11
+ _handle_custom_command,
12
+ )
13
+ from code_muse.plugins.checkpointing.rewind_shortcut import RewindKeyListener
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ _rewind_listener: RewindKeyListener | None = None
18
+
19
+
20
+ def _start_rewind_listener() -> None:
21
+ global _rewind_listener
22
+ if _rewind_listener is not None:
23
+ return
24
+
25
+ def _on_double_esc() -> None:
26
+ from code_muse.plugins.checkpointing.restore_command import (
27
+ _handle_restore_command,
28
+ )
29
+
30
+ _handle_restore_command("/restore")
31
+
32
+ _rewind_listener = RewindKeyListener(_on_double_esc)
33
+ _rewind_listener.start()
34
+
35
+
36
+ def _stop_rewind_listener() -> None:
37
+ global _rewind_listener
38
+ if _rewind_listener is not None:
39
+ _rewind_listener.stop()
40
+ _rewind_listener = None
41
+
42
+
43
+ def _on_shutdown() -> None:
44
+ _stop_rewind_listener()
45
+
46
+
47
+ # Register callbacks
48
+ register_callback("pre_tool_call", on_pre_tool_call_checkpoint)
49
+ register_callback("custom_command_help", _custom_help)
50
+ register_callback("custom_command", _handle_custom_command)
51
+ register_callback("shutdown", _on_shutdown)
@@ -0,0 +1,263 @@
1
+ """/restore slash command for checkpointing rewind."""
2
+
3
+ import logging
4
+ import subprocess
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from code_muse.tools.common import get_user_approval_async
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def _custom_help() -> list[tuple[str, str]]:
14
+ return [
15
+ (
16
+ "restore",
17
+ "List checkpoints or revert to a previous checkpoint (files and/or conversation)",
18
+ )
19
+ ]
20
+
21
+
22
+ def _handle_restore_command(command: str) -> bool:
23
+ from code_muse.messaging import emit_error, emit_info
24
+ from code_muse.plugins.checkpointing.conversation_snapshots import (
25
+ list_snapshots,
26
+ load_snapshot,
27
+ )
28
+
29
+ tokens = command.split()
30
+ if len(tokens) < 2:
31
+ # List all checkpoints
32
+ project_root = Path.cwd()
33
+ project_hash = _hash_project_root(str(project_root))
34
+ repo_path = Path.home() / ".muse" / "history" / project_hash
35
+ snapshots = list_snapshots(repo_path)
36
+ if not snapshots:
37
+ emit_info("No checkpoints available yet.")
38
+ return True
39
+
40
+ lines = [":rewind: Available checkpoints:"]
41
+ for idx, snap in enumerate(snapshots, start=1):
42
+ ts = snap.get("timestamp", "")
43
+ tool = snap.get("tool_name", "")
44
+ lines.append(f" {idx}. [{ts}] {tool}")
45
+
46
+ emit_info("\n".join(lines))
47
+ emit_info("Usage: /restore <index> [full|files|conversation]")
48
+ return True
49
+
50
+ try:
51
+ index = int(tokens[1])
52
+ except ValueError:
53
+ emit_error("/restore: index must be an integer")
54
+ return True
55
+
56
+ project_root = Path.cwd()
57
+ project_hash = _hash_project_root(str(project_root))
58
+ repo_path = Path.home() / ".muse" / "history" / project_hash
59
+ snapshots = list_snapshots(repo_path)
60
+ if not snapshots:
61
+ emit_info("No checkpoints available yet.")
62
+ return True
63
+
64
+ if index < 1 or index > len(snapshots):
65
+ emit_error(f"/restore: index {index} out of range (1–{len(snapshots)})")
66
+ return True
67
+
68
+ selected = snapshots[index - 1]
69
+ snapshot = load_snapshot(Path(selected["path"]))
70
+ if snapshot is None:
71
+ emit_error("/restore: could not load selected snapshot")
72
+ return True
73
+
74
+ scope = "preview"
75
+ if len(tokens) >= 3:
76
+ scope = tokens[2].lower()
77
+ if scope not in ("full", "files", "conversation", "preview"):
78
+ emit_error(
79
+ "/restore: scope must be one of full, files, conversation, preview"
80
+ )
81
+ return True
82
+
83
+ if scope == "preview":
84
+ _preview_checkpoint(selected, snapshot, project_root)
85
+ return True
86
+
87
+ # Confirmation dialog before destructive restore
88
+ _run_restore(scope, selected, snapshot, project_root)
89
+ return True
90
+
91
+
92
+ def _preview_checkpoint(
93
+ selected: dict[str, Any], snapshot: dict[str, Any], project_root: Path
94
+ ) -> None:
95
+ from code_muse.messaging import emit_info
96
+
97
+ commit_hash = _get_commit_hash_for_snapshot(selected, project_root)
98
+ lines = [":mag: Checkpoint preview:"]
99
+ lines.append(f" Timestamp: {selected.get('timestamp', '')}")
100
+ lines.append(f" Tool: {selected.get('tool_name', '')}")
101
+ lines.append(f" Messages: {len(snapshot.get('messages', []))} at checkpoint")
102
+
103
+ if commit_hash:
104
+ try:
105
+ diff_result = subprocess.run(
106
+ [
107
+ "git",
108
+ "-C",
109
+ str(project_root),
110
+ "diff",
111
+ f"{commit_hash}..HEAD",
112
+ "--stat",
113
+ ],
114
+ capture_output=True,
115
+ text=True,
116
+ )
117
+ if diff_result.returncode == 0 and diff_result.stdout.strip():
118
+ lines.append(" Diff since checkpoint:")
119
+ for line in diff_result.stdout.strip().splitlines():
120
+ lines.append(f" {line}")
121
+ else:
122
+ lines.append(" No file changes since checkpoint.")
123
+ except Exception as exc:
124
+ lines.append(f" Could not compute diff: {exc}")
125
+ else:
126
+ lines.append(" Commit hash not available.")
127
+
128
+ lines.append("")
129
+ lines.append("Usage: /restore <index> [full|files|conversation]")
130
+ emit_info("\n".join(lines))
131
+
132
+
133
+ def _run_restore(
134
+ scope: str,
135
+ selected: dict[str, Any],
136
+ snapshot: dict[str, Any],
137
+ project_root: Path,
138
+ ) -> None:
139
+ import asyncio
140
+
141
+ from code_muse.messaging import emit_error, emit_info, emit_success
142
+
143
+ async def _do_restore() -> bool:
144
+ # TODO: PEP 734 async bridge — _get_commit_hash_for_snapshot uses sync subprocess
145
+ commit_hash = await asyncio.to_thread(
146
+ _get_commit_hash_for_snapshot, selected, project_root
147
+ )
148
+ restore_parts: list[str] = []
149
+ if scope in ("full", "files"):
150
+ restore_parts.append("files")
151
+ if scope in ("full", "conversation"):
152
+ restore_parts.append("conversation")
153
+
154
+ preview_text = f"Revert {', '.join(restore_parts)} to checkpoint [{selected.get('timestamp', '')}]?"
155
+
156
+ confirmed, feedback = await get_user_approval_async(
157
+ title="Restore Checkpoint",
158
+ content=preview_text,
159
+ preview=None,
160
+ border_style="dim white",
161
+ agent_name="Muse",
162
+ )
163
+ if not confirmed:
164
+ emit_info("Restore cancelled.")
165
+ return False
166
+
167
+ if scope in ("full", "files") and commit_hash:
168
+ try:
169
+ checkout_proc = await asyncio.create_subprocess_exec(
170
+ "git",
171
+ "-C",
172
+ str(project_root),
173
+ "checkout",
174
+ commit_hash,
175
+ "--",
176
+ ".",
177
+ stdout=asyncio.subprocess.PIPE,
178
+ stderr=asyncio.subprocess.PIPE,
179
+ )
180
+ checkout_stdout, checkout_stderr = await checkout_proc.communicate()
181
+ if checkout_proc.returncode != 0:
182
+ emit_error(f"git checkout failed: {checkout_stderr.decode()}")
183
+ return False
184
+ emit_success(":white_check_mark: Files restored.")
185
+ except Exception as exc:
186
+ emit_error(f"File restore failed: {exc}")
187
+ return False
188
+
189
+ if scope in ("full", "conversation"):
190
+ try:
191
+ from code_muse.agents import get_current_agent
192
+
193
+ agent = get_current_agent()
194
+ messages = snapshot.get("messages", [])
195
+ if messages:
196
+ agent.set_message_history(messages)
197
+ emit_success(":white_check_mark: Conversation restored.")
198
+ else:
199
+ emit_info("No conversation state in snapshot.")
200
+ except Exception as exc:
201
+ emit_error(f"Conversation restore failed: {exc}")
202
+ return False
203
+
204
+ return True
205
+
206
+ try:
207
+ asyncio.get_running_loop()
208
+ asyncio.create_task(_do_restore())
209
+ except RuntimeError:
210
+ asyncio.run(_do_restore())
211
+
212
+
213
+ def _get_commit_hash_for_snapshot(
214
+ selected: dict[str, Any], project_root: Path
215
+ ) -> str | None:
216
+ """Find the commit hash that matches this snapshot's timestamp/tool."""
217
+ try:
218
+ timestamp = selected.get("timestamp", "")
219
+ tool_name = selected.get("tool_name", "")
220
+ log_result = subprocess.run(
221
+ [
222
+ "git",
223
+ "-C",
224
+ str(project_root),
225
+ "log",
226
+ "--oneline",
227
+ "--format=%H %s",
228
+ ],
229
+ capture_output=True,
230
+ text=True,
231
+ )
232
+ if log_result.returncode != 0:
233
+ return None
234
+ for line in log_result.stdout.strip().splitlines():
235
+ parts = line.split(None, 1)
236
+ if len(parts) < 2:
237
+ continue
238
+ commit_hash, message = parts
239
+ if f"checkpoint: {tool_name}" in message and timestamp in message:
240
+ return commit_hash
241
+ # Fallback: return HEAD if no exact match
242
+ head_result = subprocess.run(
243
+ ["git", "-C", str(project_root), "rev-parse", "HEAD"],
244
+ capture_output=True,
245
+ text=True,
246
+ )
247
+ if head_result.returncode == 0:
248
+ return head_result.stdout.strip()
249
+ except Exception as exc:
250
+ logger.warning(f"Could not resolve commit hash: {exc}")
251
+ return None
252
+
253
+
254
+ def _hash_project_root(project_root: str) -> str:
255
+ import hashlib
256
+
257
+ return hashlib.sha256(project_root.encode()).hexdigest()
258
+
259
+
260
+ def _handle_custom_command(command: str, name: str) -> bool | None:
261
+ if name != "restore":
262
+ return None
263
+ return _handle_restore_command(command)
@@ -0,0 +1,88 @@
1
+ """Esc×2 rewind keyboard shortcut."""
2
+
3
+ import logging
4
+ import sys
5
+ import threading
6
+ import time
7
+ from collections.abc import Callable
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class DoublePressDetector:
13
+ """Detects double-press of a key within a time window."""
14
+
15
+ def __init__(self, window_ms: int = 500) -> None:
16
+ self._window_ms = window_ms
17
+ self._last_press: float = 0.0
18
+
19
+ def press(self) -> bool:
20
+ now = time.monotonic()
21
+ delta_ms = (now - self._last_press) * 1000
22
+ self._last_press = now
23
+ return 0 < delta_ms < self._window_ms
24
+
25
+
26
+ class RewindKeyListener:
27
+ """Daemon thread that listens for raw ESC keycodes and triggers rewind on double-press."""
28
+
29
+ def __init__(self, on_double_esc: Callable[[], None]) -> None:
30
+ self._detector = DoublePressDetector()
31
+ self._on_double_esc = on_double_esc
32
+ self._stop = threading.Event()
33
+ self._thread = threading.Thread(target=self._listen, daemon=True)
34
+
35
+ def start(self) -> None:
36
+ self._thread.start()
37
+ logger.info("RewindKeyListener started")
38
+
39
+ def stop(self) -> None:
40
+ self._stop.set()
41
+ logger.info("RewindKeyListener stopped")
42
+
43
+ def _listen(self) -> None:
44
+ if sys.platform == "win32":
45
+ self._listen_windows()
46
+ else:
47
+ self._listen_posix()
48
+
49
+ def _listen_posix(self) -> None:
50
+ try:
51
+ import select
52
+ import termios
53
+ import tty
54
+
55
+ fd = sys.stdin.fileno()
56
+ old_settings = termios.tcgetattr(fd)
57
+ tty.setcbreak(fd)
58
+ try:
59
+ while not self._stop.is_set():
60
+ ready, _, _ = select.select([sys.stdin], [], [], 0.1)
61
+ if ready:
62
+ char = sys.stdin.read(1)
63
+ if char == "\x1b":
64
+ if self._detector.press():
65
+ try:
66
+ self._on_double_esc()
67
+ except Exception as exc:
68
+ logger.error(f"Double-esc handler failed: {exc}")
69
+ finally:
70
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
71
+ except Exception as exc:
72
+ logger.debug(f"POSIX rewind listener could not start: {exc}")
73
+
74
+ def _listen_windows(self) -> None:
75
+ try:
76
+ import msvcrt
77
+
78
+ while not self._stop.is_set():
79
+ if msvcrt.kbhit():
80
+ char = msvcrt.getch()
81
+ if char == b"\x1b" and self._detector.press():
82
+ try:
83
+ self._on_double_esc()
84
+ except Exception as exc:
85
+ logger.error(f"Double-esc handler failed: {exc}")
86
+ time.sleep(0.05)
87
+ except Exception as exc:
88
+ logger.debug(f"Windows rewind listener could not start: {exc}")
@@ -0,0 +1,90 @@
1
+ """Shadow git repository for checkpointing file snapshots."""
2
+
3
+ import hashlib
4
+ import logging
5
+ import subprocess
6
+ from datetime import UTC, datetime
7
+ from pathlib import Path
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class ShadowGit:
13
+ """Manages a bare shadow git repository for project checkpointing."""
14
+
15
+ def __init__(self, project_root: str) -> None:
16
+ self.project_root = Path(project_root).resolve()
17
+ project_hash = hashlib.sha256(str(self.project_root).encode()).hexdigest()
18
+ self.repo_path = Path.home() / ".muse" / "history" / project_hash
19
+ self._init_repo()
20
+
21
+ def _init_repo(self) -> None:
22
+ if not self.repo_path.exists():
23
+ try:
24
+ self.repo_path.mkdir(parents=True, exist_ok=True)
25
+ subprocess.run(
26
+ ["git", "init", "--bare", str(self.repo_path)],
27
+ capture_output=True,
28
+ text=True,
29
+ check=True,
30
+ )
31
+ logger.info(f"Initialized shadow git repo at {self.repo_path}")
32
+ except Exception as exc:
33
+ logger.warning(f"Failed to init shadow git repo: {exc}")
34
+
35
+ def create_checkpoint(
36
+ self, tool_name: str, affected_files: list[str] | None = None
37
+ ) -> str | None:
38
+ """Create a git checkpoint and return the commit hash."""
39
+ timestamp = datetime.now(UTC).isoformat()
40
+ message = f"checkpoint: {tool_name} {timestamp}"
41
+ if affected_files:
42
+ files_str = ", ".join(affected_files)
43
+ message += f" ({files_str})"
44
+
45
+ try:
46
+ # Stage everything in the project root
47
+ add_result = subprocess.run(
48
+ ["git", "-C", str(self.project_root), "add", "-A"],
49
+ capture_output=True,
50
+ text=True,
51
+ )
52
+ if add_result.returncode != 0:
53
+ logger.warning(f"git add failed: {add_result.stderr}")
54
+ return None
55
+
56
+ # Commit
57
+ commit_result = subprocess.run(
58
+ [
59
+ "git",
60
+ "-C",
61
+ str(self.project_root),
62
+ "commit",
63
+ "--allow-empty",
64
+ "-m",
65
+ message,
66
+ ],
67
+ capture_output=True,
68
+ text=True,
69
+ )
70
+ if commit_result.returncode != 0:
71
+ # No changes to commit is acceptable
72
+ if "nothing to commit" in commit_result.stdout.lower():
73
+ pass
74
+ else:
75
+ logger.warning(f"git commit failed: {commit_result.stderr}")
76
+ return None
77
+
78
+ # Get commit hash
79
+ hash_result = subprocess.run(
80
+ ["git", "-C", str(self.project_root), "rev-parse", "HEAD"],
81
+ capture_output=True,
82
+ text=True,
83
+ check=True,
84
+ )
85
+ commit_hash = hash_result.stdout.strip()
86
+ logger.info(f"Created checkpoint {commit_hash} for {tool_name}")
87
+ return commit_hash
88
+ except Exception as exc:
89
+ logger.warning(f"Checkpoint creation failed: {exc}")
90
+ return None
@@ -0,0 +1 @@
1
+ """Claude Code hooks plugin."""