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,431 @@
1
+ # cython: language_level=3
2
+ """Test compression strategies for the filter engine.
3
+
4
+ Supports pytest, vitest/jest, cargo test, and generic test runners.
5
+ """
6
+
7
+ import json
8
+ import re
9
+ from typing import Any
10
+
11
+ from code_muse.plugins.filter_engine.registry import get_registry
12
+ from code_muse.plugins.filter_engine.verbosity import VerbosityLevel
13
+ from code_muse.tools.command_runner import ShellCommandOutput
14
+
15
+
16
+ # Pre-compiled regex patterns used in the hot loops.
17
+ _pytest_result_pattern = re.compile(
18
+ r"(\S+)\s+(PASSED|FAILED|ERROR|SKIPPED|XFAIL|XPASS)"
19
+ )
20
+ _failure_start_pattern = re.compile(r"^(FAILED |ERROR )")
21
+ _generic_passed_pattern = re.compile(r"\S+\s+(PASSED|SKIPPED)")
22
+ _vitest_summary_pattern = re.compile(r"Tests?\s+\d+\s+passed|failed", re.IGNORECASE)
23
+ _dispatcher_pytest_pattern = re.compile(r"pytest\b|python\s+-m\s+pytest\b")
24
+ _dispatcher_vitest_pattern = re.compile(r"vitest\b|jest\b|npx\s+(jest|vitest)\b")
25
+ _dispatcher_cargo_pattern = re.compile(r"cargo\s+test\b")
26
+
27
+
28
+ def _extract_pytest_summary(lines: list[str]) -> str:
29
+ """Extract the pytest summary line (e.g. ``= 2 passed, 1 failed in 0.5s =``)."""
30
+ cdef str line
31
+ for line in reversed(lines):
32
+ if line.strip().startswith("=") and "passed" in line:
33
+ return line.strip()
34
+ return ""
35
+
36
+
37
+ def compress_pytest(
38
+ stdout: str,
39
+ stderr: str,
40
+ verbosity: VerbosityLevel,
41
+ ) -> ShellCommandOutput:
42
+ """Compress pytest text output.
43
+
44
+ State-machine style parser that tracks PASS/FAIL/SKIP/ERROR/XFAIL/XPASS.
45
+
46
+ * Compact (default): show FAILURES + final summary line.
47
+ * Verbose: show all results with filenames.
48
+ * Very-verbose / raw: full output (handled by caller before invocation).
49
+
50
+ Args:
51
+ stdout: Raw pytest stdout.
52
+ stderr: Raw pytest stderr.
53
+ verbosity: Current verbosity level.
54
+
55
+ Returns:
56
+ Compressed :class:`ShellCommandOutput`.
57
+ """
58
+ cdef list lines = stdout.splitlines()
59
+ cdef str summary = _extract_pytest_summary(lines)
60
+
61
+ cdef list failures = []
62
+ cdef list all_results = []
63
+
64
+ cdef bint in_failure = False
65
+ cdef list current_failure = []
66
+
67
+ cdef str line
68
+ cdef str stripped
69
+ cdef object result_match
70
+
71
+ for line in lines:
72
+ stripped = line.rstrip("\r")
73
+
74
+ # Detect test result lines
75
+ result_match = _pytest_result_pattern.search(stripped)
76
+ if result_match:
77
+ all_results.append(stripped)
78
+ if result_match.group(2) in ("FAILED", "ERROR"):
79
+ failures.append(stripped)
80
+ continue
81
+
82
+ # Collect failure detail blocks
83
+ if _failure_start_pattern.match(stripped):
84
+ in_failure = True
85
+ current_failure = [stripped]
86
+ continue
87
+
88
+ if in_failure:
89
+ if stripped.startswith("=") or _generic_passed_pattern.search(stripped):
90
+ # End of failure block
91
+ if current_failure:
92
+ failures.append("\n".join(current_failure))
93
+ in_failure = False
94
+ current_failure = []
95
+ else:
96
+ current_failure.append(stripped)
97
+
98
+ if in_failure and current_failure:
99
+ failures.append("\n".join(current_failure))
100
+
101
+ cdef str compressed
102
+ cdef list parts
103
+ if verbosity >= VerbosityLevel.VERBOSE:
104
+ parts = all_results if all_results else lines
105
+ if summary:
106
+ parts.append(f"\n{summary}")
107
+ compressed = "\n".join(parts)
108
+ else:
109
+ parts = failures if failures else [summary] if summary else lines[:5]
110
+ if summary and summary not in parts:
111
+ parts.append(summary)
112
+ compressed = "\n".join(parts)
113
+
114
+ if not compressed.strip():
115
+ compressed = stdout[:512] # fallback: first 512 chars
116
+
117
+ cdef bint has_error = bool(stderr.strip())
118
+ return ShellCommandOutput(
119
+ success=not has_error,
120
+ command="pytest",
121
+ stdout=compressed,
122
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
123
+ exit_code=0 if not has_error else 1,
124
+ execution_time=None,
125
+ )
126
+
127
+
128
+ def compress_vitest_jest(
129
+ stdout: str,
130
+ stderr: str,
131
+ verbosity: VerbosityLevel,
132
+ ) -> ShellCommandOutput:
133
+ """Compress vitest/jest output.
134
+
135
+ Attempts JSON parsing first, then falls back to text parsing.
136
+ Failures are always surfaced; passes are hidden at compact levels.
137
+
138
+ Args:
139
+ stdout: Raw stdout.
140
+ stderr: Raw stderr.
141
+ verbosity: Current verbosity level.
142
+
143
+ Returns:
144
+ Compressed :class:`ShellCommandOutput`.
145
+ """
146
+ # Try JSON mode first
147
+ try:
148
+ data = json.loads(stdout)
149
+ if isinstance(data, dict) and "testResults" in data:
150
+ return _compress_jest_json(data, stderr, verbosity)
151
+ except ValueError:
152
+ pass
153
+
154
+ # Fallback to text parsing
155
+ cdef list lines = stdout.splitlines()
156
+ cdef list failures = []
157
+ cdef str summary = ""
158
+
159
+ cdef str line
160
+ cdef str stripped
161
+
162
+ for line in lines:
163
+ stripped = line.rstrip("\r")
164
+ if "FAIL" in stripped or "✕" in stripped:
165
+ failures.append(stripped)
166
+ if _vitest_summary_pattern.search(stripped):
167
+ summary = stripped
168
+
169
+ cdef str compressed
170
+ if verbosity >= VerbosityLevel.VERBOSE:
171
+ compressed = stdout
172
+ else:
173
+ parts = (
174
+ failures + [summary]
175
+ if failures and summary
176
+ else failures
177
+ if failures
178
+ else [summary]
179
+ if summary
180
+ else lines[:5]
181
+ )
182
+ compressed = "\n".join(parts)
183
+
184
+ cdef bint has_error = bool(failures) or bool(stderr.strip())
185
+ return ShellCommandOutput(
186
+ success=not has_error,
187
+ command="vitest/jest",
188
+ stdout=compressed,
189
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
190
+ exit_code=0 if not has_error else 1,
191
+ execution_time=None,
192
+ )
193
+
194
+
195
+ def _compress_jest_json(
196
+ data: dict[str, Any],
197
+ stderr: str,
198
+ verbosity: VerbosityLevel,
199
+ ) -> ShellCommandOutput:
200
+ """Compress jest JSON output.
201
+
202
+ Args:
203
+ data: Parsed JSON test results.
204
+ stderr: Raw stderr.
205
+ verbosity: Current verbosity level.
206
+
207
+ Returns:
208
+ Compressed :class:`ShellCommandOutput`.
209
+ """
210
+ cdef list results = data.get("testResults", [])
211
+ cdef list failures = []
212
+ cdef int total_tests = 0
213
+ cdef int passed_tests = 0
214
+ cdef int failed_tests = 0
215
+
216
+ cdef dict suite
217
+ cdef list assertions
218
+ cdef dict test
219
+ cdef str status
220
+ cdef str title
221
+
222
+ for suite in results:
223
+ assertions = suite.get("assertionResults", [])
224
+ for test in assertions:
225
+ total_tests += 1
226
+ status = test.get("status", "")
227
+ title = test.get("title", "unknown")
228
+ if status == "passed":
229
+ passed_tests += 1
230
+ else:
231
+ failed_tests += 1
232
+ failures.append(f" FAIL {title}")
233
+
234
+ cdef str summary = f"{total_tests} tests, {passed_tests} passed, {failed_tests} failed"
235
+
236
+ cdef str compressed
237
+ if verbosity >= VerbosityLevel.VERBOSE:
238
+ compressed = summary + "\n" + "\n".join(failures)
239
+ else:
240
+ parts = failures + [summary] if failures else [summary]
241
+ compressed = "\n".join(parts)
242
+
243
+ return ShellCommandOutput(
244
+ success=failed_tests == 0,
245
+ command="jest",
246
+ stdout=compressed,
247
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
248
+ exit_code=0 if failed_tests == 0 else 1,
249
+ execution_time=None,
250
+ )
251
+
252
+
253
+ def compress_cargo_test(
254
+ stdout: str,
255
+ stderr: str,
256
+ verbosity: VerbosityLevel,
257
+ ) -> ShellCommandOutput:
258
+ """Compress ``cargo test`` output.
259
+
260
+ Parses both text and NDJSON (``--message-format=json``) output.
261
+
262
+ Args:
263
+ stdout: Raw stdout.
264
+ stderr: Raw stderr.
265
+ verbosity: Current verbosity level.
266
+
267
+ Returns:
268
+ Compressed :class:`ShellCommandOutput`.
269
+ """
270
+ # Try NDJSON first
271
+ cdef list json_lines = [line for line in stdout.splitlines() if line.strip().startswith("{")]
272
+ if json_lines:
273
+ return _compress_cargo_ndjson(json_lines, stderr, verbosity)
274
+
275
+ # Text parsing
276
+ cdef list lines = stdout.splitlines()
277
+ cdef list failures = []
278
+ cdef str summary = ""
279
+
280
+ cdef str line
281
+ cdef str stripped
282
+
283
+ for line in lines:
284
+ stripped = line.rstrip("\r")
285
+ if stripped.startswith("test result:"):
286
+ summary = stripped
287
+ elif "FAILED" in stripped and stripped.startswith("test "):
288
+ failures.append(stripped)
289
+
290
+ cdef str compressed
291
+ if verbosity >= VerbosityLevel.VERBOSE:
292
+ compressed = stdout
293
+ else:
294
+ parts = failures if failures else [summary] if summary else lines[:5]
295
+ if summary and summary not in parts:
296
+ parts.append(summary)
297
+ compressed = "\n".join(parts)
298
+
299
+ cdef bint has_error = bool(failures) or bool(stderr.strip())
300
+ return ShellCommandOutput(
301
+ success=not has_error,
302
+ command="cargo test",
303
+ stdout=compressed,
304
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
305
+ exit_code=0 if not has_error else 1,
306
+ execution_time=None,
307
+ )
308
+
309
+
310
+ def _compress_cargo_ndjson(
311
+ json_lines: list[str],
312
+ stderr: str,
313
+ verbosity: VerbosityLevel,
314
+ ) -> ShellCommandOutput:
315
+ """Compress cargo NDJSON output.
316
+
317
+ Args:
318
+ json_lines: Lines that look like JSON objects.
319
+ stderr: Raw stderr.
320
+ verbosity: Current verbosity level.
321
+
322
+ Returns:
323
+ Compressed :class:`ShellCommandOutput`.
324
+ """
325
+ cdef list failures = []
326
+ cdef int total = 0
327
+ cdef int passed = 0
328
+ cdef int failed = 0
329
+
330
+ cdef str line
331
+ cdef dict obj
332
+ cdef str event
333
+ cdef str name
334
+
335
+ for line in json_lines:
336
+ try:
337
+ obj = json.loads(line)
338
+ event = obj.get("event", "")
339
+ if event == "started":
340
+ total += 1
341
+ elif event == "ok":
342
+ passed += 1
343
+ elif event in ("failed", "error"):
344
+ failed += 1
345
+ name = obj.get("name", "unknown")
346
+ failures.append(f" FAIL {name}")
347
+ except ValueError:
348
+ continue
349
+
350
+ cdef str summary = f"{total} tests, {passed} passed, {failed} failed"
351
+
352
+ cdef str compressed
353
+ if verbosity >= VerbosityLevel.VERBOSE:
354
+ compressed = summary + "\n" + "\n".join(failures)
355
+ else:
356
+ parts = failures + [summary] if failures else [summary]
357
+ compressed = "\n".join(parts)
358
+
359
+ return ShellCommandOutput(
360
+ success=failed == 0,
361
+ command="cargo test",
362
+ stdout=compressed,
363
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
364
+ exit_code=0 if failed == 0 else 1,
365
+ execution_time=None,
366
+ )
367
+
368
+
369
+ def compress_test(
370
+ command: str,
371
+ stdout: str,
372
+ stderr: str,
373
+ exit_code: int,
374
+ verbosity: VerbosityLevel,
375
+ ) -> ShellCommandOutput | None:
376
+ """Main dispatcher for test compression strategies.
377
+
378
+ Args:
379
+ command: The original test command.
380
+ stdout: Raw stdout.
381
+ stderr: Raw stderr.
382
+ exit_code: Process exit code.
383
+ verbosity: Current verbosity level.
384
+
385
+ Returns:
386
+ A compressed :class:`ShellCommandOutput` or ``None``.
387
+ """
388
+ cdef str stripped = command.strip()
389
+ cdef object out
390
+
391
+ if _dispatcher_pytest_pattern.search(stripped):
392
+ out = compress_pytest(stdout, stderr, verbosity)
393
+ out.exit_code = exit_code
394
+ return out
395
+
396
+ if _dispatcher_vitest_pattern.search(stripped):
397
+ out = compress_vitest_jest(stdout, stderr, verbosity)
398
+ out.exit_code = exit_code
399
+ return out
400
+
401
+ if _dispatcher_cargo_pattern.search(stripped):
402
+ out = compress_cargo_test(stdout, stderr, verbosity)
403
+ out.exit_code = exit_code
404
+ return out
405
+
406
+ # Generic fallback: keep failures + summary heuristic
407
+ cdef list lines = stdout.splitlines()
408
+ cdef list failures = [line for line in lines if "FAIL" in line or "failed" in line.lower()]
409
+ cdef str summary = next((line for line in reversed(lines) if "passed" in line.lower()), "")
410
+
411
+ cdef str compressed
412
+ if verbosity >= VerbosityLevel.VERBOSE:
413
+ compressed = stdout
414
+ else:
415
+ parts = failures if failures else [summary] if summary else lines[:5]
416
+ compressed = "\n".join(parts)
417
+
418
+ return ShellCommandOutput(
419
+ success=exit_code == 0,
420
+ command=stripped,
421
+ stdout=compressed,
422
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
423
+ exit_code=exit_code,
424
+ execution_time=None,
425
+ )
426
+
427
+
428
+ # ---------------------------------------------------------------------------
429
+ # Register with the strategy registry
430
+ # ---------------------------------------------------------------------------
431
+ get_registry().register("test", compress_test, priority=0)
@@ -0,0 +1,63 @@
1
+ """Verbosity configuration for the filter engine.
2
+
3
+ Defines verbosity levels and provides helpers to read the current level
4
+ from CLI flags or the ``FAST_PUPPY_VERBOSITY`` environment variable.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ from enum import IntEnum
10
+
11
+
12
+ class VerbosityLevel(IntEnum):
13
+ """Verbosity levels for filter output compression.
14
+
15
+ Attributes:
16
+ ULTRA_COMPACT: Minimal output — single-line summaries only.
17
+ COMPACT: Default — concise summaries with key counts.
18
+ VERBOSE: Include file lists and short excerpts.
19
+ VERY_VERBOSE: Include full output sections.
20
+ RAW: Disable all filtering — passthrough raw output.
21
+ """
22
+
23
+ ULTRA_COMPACT = 0 # -u
24
+ COMPACT = 1 # default (no flag)
25
+ VERBOSE = 2 # -v
26
+ VERY_VERBOSE = 3 # -vv
27
+ RAW = 4 # -vvv
28
+
29
+
30
+ def get_verbosity() -> VerbosityLevel:
31
+ """Determine the current verbosity level.
32
+
33
+ Resolution order:
34
+
35
+ 1. Check ``sys.argv`` for ``-u``/``--ultra-compact``, ``-v``, ``-vv``, ``-vvv``.
36
+ 2. Check the ``FAST_PUPPY_VERBOSITY`` environment variable (``0``–``4``).
37
+ 3. Default to ``VerbosityLevel.COMPACT``.
38
+
39
+ Returns:
40
+ The resolved verbosity level.
41
+ """
42
+ # CLI flag parsing
43
+ args = sys.argv
44
+ if "-u" in args or "--ultra-compact" in args:
45
+ return VerbosityLevel.ULTRA_COMPACT
46
+ if "-vvv" in args:
47
+ return VerbosityLevel.RAW
48
+ if "-vv" in args:
49
+ return VerbosityLevel.VERY_VERBOSE
50
+ if "-v" in args:
51
+ return VerbosityLevel.VERBOSE
52
+
53
+ # Environment variable
54
+ env_val = os.environ.get("FAST_PUPPY_VERBOSITY")
55
+ if env_val is not None:
56
+ try:
57
+ level = int(env_val)
58
+ if 0 <= level <= 4:
59
+ return VerbosityLevel(level)
60
+ except ValueError:
61
+ pass
62
+
63
+ return VerbosityLevel.COMPACT
@@ -0,0 +1,5 @@
1
+ """Force push guard plugin.
2
+
3
+ Blocks git force push commands and asks for explicit permission before
4
+ allowing them through. Prevents the classic "oops I nuked main" scenario.
5
+ """
@@ -0,0 +1,96 @@
1
+ """Pattern detection for git force push commands.
2
+
3
+ Detects force push patterns in shell commands, covering all the sneaky
4
+ ways git lets you wreck a remote branch.
5
+ """
6
+
7
+ import re
8
+ from dataclasses import dataclass
9
+
10
+
11
+ @dataclass
12
+ class ForcePushMatch:
13
+ """Result of a force push pattern match."""
14
+
15
+ pattern_name: str
16
+ description: str
17
+
18
+
19
+ # Matches shell operators that precede a new command in a pipeline/chain.
20
+ # E.g. "cd foo && git push --force" or "true || git push -f"
21
+ _SHELL_OPERATOR_RE = re.compile(r"(?:^|&&|\|\||;|\|)\s*git\s+push\b", re.MULTILINE)
22
+
23
+ # Ordered by specificity — first match wins.
24
+ # Each tuple: (compiled regex, human-readable name, what it catches)
25
+ _FORCE_PUSH_PATTERNS: list[tuple[re.Pattern, str, str]] = [
26
+ (
27
+ re.compile(r"\bgit\s+push\b.*--force-with-lease"),
28
+ "--force-with-lease",
29
+ "force push with lease (safer, but still rewrites history)",
30
+ ),
31
+ (
32
+ re.compile(r"\bgit\s+push\b.*--force-if-includes"),
33
+ "--force-if-includes",
34
+ "force push with includes check (still rewrites history)",
35
+ ),
36
+ (
37
+ re.compile(r"\bgit\s+push\b.*--force"),
38
+ "--force",
39
+ "force push (rewrites remote history)",
40
+ ),
41
+ (
42
+ re.compile(r"\bgit\s+push\b.*\s-f\b"),
43
+ "-f",
44
+ "force push shorthand (rewrites remote history)",
45
+ ),
46
+ (
47
+ re.compile(r"\bgit\s+push\b.*\s-F\b"),
48
+ "-F",
49
+ "force push shorthand (rewrites remote history)",
50
+ ),
51
+ # The +refspec syntax: git push origin +main, git push origin +HEAD:main
52
+ (
53
+ re.compile(r"\bgit\s+push\b.*\s\+"),
54
+ "+refspec",
55
+ "force push via +refspec prefix (rewrites remote history)",
56
+ ),
57
+ ]
58
+
59
+
60
+ def _is_git_push_a_command(command: str) -> bool:
61
+ """Check that 'git push' is an actual command, not a string argument.
62
+
63
+ Handles compound commands like "cd foo && git push --force" while
64
+ avoiding false positives like "echo 'git push --force'".
65
+
66
+ Args:
67
+ command: The shell command string to inspect.
68
+
69
+ Returns:
70
+ True if 'git push' appears as an actual command invocation.
71
+ """
72
+ return bool(_SHELL_OPERATOR_RE.search(command))
73
+
74
+
75
+ def detect_force_push(command: str) -> ForcePushMatch | None:
76
+ """Check if a shell command contains a git force push.
77
+
78
+ Args:
79
+ command: The shell command string to inspect.
80
+
81
+ Returns:
82
+ ForcePushMatch if a force push pattern is found, None otherwise.
83
+ """
84
+ # Quick pre-filter: skip entirely if "push" isn't even in the command
85
+ if "push" not in command:
86
+ return None
87
+
88
+ # Ensure 'git push' is an actual command, not a string argument
89
+ if not _is_git_push_a_command(command):
90
+ return None
91
+
92
+ for pattern, name, description in _FORCE_PUSH_PATTERNS:
93
+ if pattern.search(command):
94
+ return ForcePushMatch(pattern_name=name, description=description)
95
+
96
+ return None