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,626 @@
1
+ """Lint compression strategies for the filter engine.
2
+
3
+ Groups linter output by rule code / message type to produce compact summaries.
4
+ Also handles ``grep`` and ``find`` output for the lint category.
5
+ """
6
+
7
+ import json
8
+ import re
9
+ from collections import defaultdict
10
+ from typing import Any
11
+
12
+ from code_muse.plugins.filter_engine.registry import get_registry
13
+ from code_muse.plugins.filter_engine.verbosity import VerbosityLevel
14
+ from code_muse.tools.command_runner import ShellCommandOutput
15
+
16
+
17
+ _RULE_PATTERN = re.compile(r"\b([A-Z]+\d+)\b")
18
+
19
+
20
+ def compress_ruff(
21
+ stdout: str,
22
+ stderr: str,
23
+ verbosity: VerbosityLevel,
24
+ ) -> ShellCommandOutput:
25
+ """Compress ruff text output by grouping rule codes.
26
+
27
+ Format::
28
+
29
+ E501: 5 files, 12 occurrences
30
+ F841: 3 files, 4 occurrences
31
+
32
+ Args:
33
+ stdout: Raw ruff stdout.
34
+ stderr: Raw ruff stderr.
35
+ verbosity: Current verbosity level.
36
+
37
+ Returns:
38
+ Compressed :class:`ShellCommandOutput`.
39
+ """
40
+ # Try JSON first
41
+ try:
42
+ data = json.loads(stdout)
43
+ if isinstance(data, list):
44
+ return _compress_ruff_json(data, stderr, verbosity)
45
+ except ValueError:
46
+ pass
47
+
48
+ lines = stdout.splitlines()
49
+ rule_counts = defaultdict(lambda: {"files": set(), "count": 0})
50
+
51
+ # Pattern: file.py:5:1: E501 Line too long
52
+ pattern = re.compile(r"^(.+?):\d+:\d+:\s*([A-Z]\d+)\s+(.*)")
53
+
54
+ cdef str line
55
+ cdef object match
56
+ cdef str filepath
57
+ cdef str rule
58
+
59
+ for line in lines:
60
+ match = pattern.match(line.rstrip("\r"))
61
+ if match:
62
+ filepath = match.group(1)
63
+ rule = match.group(2)
64
+ rule_counts[rule]["files"].add(filepath)
65
+ rule_counts[rule]["count"] += 1
66
+
67
+ if not rule_counts:
68
+ return ShellCommandOutput(
69
+ success=True,
70
+ command="ruff",
71
+ stdout=stdout.strip() or "No issues found",
72
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
73
+ exit_code=0,
74
+ execution_time=None,
75
+ )
76
+
77
+ parts: list[str] = []
78
+ for rule in sorted(rule_counts.keys()):
79
+ info = rule_counts[rule]
80
+ parts.append(f"{rule}: {len(info['files'])} files, {info['count']} occurrences")
81
+ if verbosity >= VerbosityLevel.VERBOSE:
82
+ for fp in sorted(info["files"])[:10]:
83
+ parts.append(f" {fp}")
84
+ if len(info["files"]) > 10:
85
+ parts.append(f" ... and {len(info['files']) - 10} more files")
86
+
87
+ compressed = "\n".join(parts)
88
+
89
+ return ShellCommandOutput(
90
+ success=True,
91
+ command="ruff",
92
+ stdout=compressed,
93
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
94
+ exit_code=0,
95
+ execution_time=None,
96
+ )
97
+
98
+
99
+ def _compress_ruff_json(
100
+ data: list[dict[str, Any]],
101
+ stderr: str,
102
+ verbosity: VerbosityLevel,
103
+ ) -> ShellCommandOutput:
104
+ """Compress ruff JSON output.
105
+
106
+ Args:
107
+ data: List of violation dicts from ruff ``--format=json``.
108
+ stderr: Raw stderr.
109
+ verbosity: Current verbosity level.
110
+
111
+ Returns:
112
+ Compressed :class:`ShellCommandOutput`.
113
+ """
114
+ rule_counts = defaultdict(lambda: {"files": set(), "count": 0})
115
+
116
+ cdef str rule
117
+ cdef str filepath
118
+ cdef dict violation
119
+
120
+ for violation in data:
121
+ rule = violation.get("code", "UNKNOWN")
122
+ filepath = violation.get("filename", "unknown")
123
+ rule_counts[rule]["files"].add(filepath)
124
+ rule_counts[rule]["count"] += 1
125
+
126
+ parts: list[str] = []
127
+ for rule in sorted(rule_counts.keys()):
128
+ info = rule_counts[rule]
129
+ parts.append(f"{rule}: {len(info['files'])} files, {info['count']} occurrences")
130
+ if verbosity >= VerbosityLevel.VERBOSE:
131
+ for fp in sorted(info["files"])[:10]:
132
+ parts.append(f" {fp}")
133
+ if len(info["files"]) > 10:
134
+ parts.append(f" ... and {len(info['files']) - 10} more files")
135
+
136
+ return ShellCommandOutput(
137
+ success=len(data) == 0,
138
+ command="ruff",
139
+ stdout="\n".join(parts) if parts else "No issues found",
140
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
141
+ exit_code=0 if not data else 1,
142
+ execution_time=None,
143
+ )
144
+
145
+
146
+ def compress_eslint(
147
+ stdout: str,
148
+ stderr: str,
149
+ verbosity: VerbosityLevel,
150
+ ) -> ShellCommandOutput:
151
+ """Compress eslint output by grouping rule/message type.
152
+
153
+ Args:
154
+ stdout: Raw eslint stdout.
155
+ stderr: Raw eslint stderr.
156
+ verbosity: Current verbosity level.
157
+
158
+ Returns:
159
+ Compressed :class:`ShellCommandOutput`.
160
+ """
161
+ # Try JSON first
162
+ try:
163
+ data = json.loads(stdout)
164
+ if isinstance(data, list):
165
+ return _compress_eslint_json(data, stderr, verbosity)
166
+ except ValueError:
167
+ pass
168
+
169
+ lines = stdout.splitlines()
170
+ rule_counts = defaultdict(lambda: {"files": set(), "count": 0})
171
+
172
+ # Pattern: file.js:5:3: error Some message [rule-id]
173
+ pattern = re.compile(r"^(.+?):\d+:\d+:\s*(error|warning|info)\s+(.*?)\s*\[(\S+)\]")
174
+
175
+ cdef str line
176
+ cdef object match
177
+ cdef str filepath
178
+ cdef str severity
179
+ cdef str rule
180
+ cdef str key
181
+
182
+ for line in lines:
183
+ match = pattern.match(line.rstrip("\r"))
184
+ if match:
185
+ filepath = match.group(1)
186
+ severity = match.group(2)
187
+ rule = match.group(4)
188
+ key = f"{severity}:{rule}"
189
+ rule_counts[key]["files"].add(filepath)
190
+ rule_counts[key]["count"] += 1
191
+
192
+ if not rule_counts:
193
+ return ShellCommandOutput(
194
+ success=True,
195
+ command="eslint",
196
+ stdout=stdout.strip() or "No issues found",
197
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
198
+ exit_code=0,
199
+ execution_time=None,
200
+ )
201
+
202
+ parts: list[str] = []
203
+ for key in sorted(rule_counts.keys()):
204
+ info = rule_counts[key]
205
+ parts.append(f"{key}: {len(info['files'])} files, {info['count']} occurrences")
206
+ if verbosity >= VerbosityLevel.VERBOSE:
207
+ for fp in sorted(info["files"])[:10]:
208
+ parts.append(f" {fp}")
209
+ if len(info["files"]) > 10:
210
+ parts.append(f" ... and {len(info['files']) - 10} more files")
211
+
212
+ return ShellCommandOutput(
213
+ success="error:" not in stdout.lower(),
214
+ command="eslint",
215
+ stdout="\n".join(parts),
216
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
217
+ exit_code=0,
218
+ execution_time=None,
219
+ )
220
+
221
+
222
+ def _compress_eslint_json(
223
+ data: list[dict[str, Any]],
224
+ stderr: str,
225
+ verbosity: VerbosityLevel,
226
+ ) -> ShellCommandOutput:
227
+ """Compress eslint JSON output.
228
+
229
+ Args:
230
+ data: List of eslint result dicts.
231
+ stderr: Raw stderr.
232
+ verbosity: Current verbosity level.
233
+
234
+ Returns:
235
+ Compressed :class:`ShellCommandOutput`.
236
+ """
237
+ rule_counts = defaultdict(lambda: {"files": set(), "count": 0})
238
+
239
+ cdef dict result
240
+ cdef str filepath
241
+ cdef dict msg
242
+ cdef int severity
243
+ cdef str rule
244
+ cdef str sev_str
245
+ cdef str key
246
+
247
+ for result in data:
248
+ filepath = result.get("filePath", "unknown")
249
+ for msg in result.get("messages", []):
250
+ severity = msg.get("severity", 0)
251
+ rule = msg.get("ruleId", "UNKNOWN")
252
+ sev_str = {1: "warning", 2: "error"}.get(severity, "info")
253
+ key = f"{sev_str}:{rule}"
254
+ rule_counts[key]["files"].add(filepath)
255
+ rule_counts[key]["count"] += 1
256
+
257
+ parts: list[str] = []
258
+ for key in sorted(rule_counts.keys()):
259
+ info = rule_counts[key]
260
+ parts.append(f"{key}: {len(info['files'])} files, {info['count']} occurrences")
261
+
262
+ return ShellCommandOutput(
263
+ success=not any(k.startswith("error:") for k in rule_counts),
264
+ command="eslint",
265
+ stdout="\n".join(parts) if parts else "No issues found",
266
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
267
+ exit_code=0,
268
+ execution_time=None,
269
+ )
270
+
271
+
272
+ def compress_golangci(
273
+ stdout: str,
274
+ stderr: str,
275
+ verbosity: VerbosityLevel,
276
+ ) -> ShellCommandOutput:
277
+ """Compress golangci-lint output.
278
+
279
+ Tries ``--out-format=json`` first, then falls back to text.
280
+
281
+ Args:
282
+ stdout: Raw stdout.
283
+ stderr: Raw stderr.
284
+ verbosity: Current verbosity level.
285
+
286
+ Returns:
287
+ Compressed :class:`ShellCommandOutput`.
288
+ """
289
+ # Try JSON first
290
+ try:
291
+ data = json.loads(stdout)
292
+ if isinstance(data, dict) and "Issues" in data:
293
+ return _compress_golangci_json(data, stderr, verbosity)
294
+ except ValueError:
295
+ pass
296
+
297
+ lines = stdout.splitlines()
298
+ rule_counts = defaultdict(lambda: {"files": set(), "count": 0})
299
+
300
+ # Pattern: file.go:5:3: message (rule-id)
301
+ pattern = re.compile(r"^(.+?):\d+:\d+:\s*(.*)")
302
+
303
+ cdef str line
304
+ cdef object match
305
+ cdef str filepath
306
+ cdef str rest
307
+ cdef object rule_match
308
+ cdef str rule
309
+
310
+ for line in lines:
311
+ match = pattern.match(line.rstrip("\r"))
312
+ if match:
313
+ filepath = match.group(1)
314
+ rest = match.group(2)
315
+ # Extract rule-id from parentheses if present
316
+ rule_match = re.search(r"\((\S+)\)$", rest)
317
+ if rule_match:
318
+ rule = rule_match.group(1)
319
+ else:
320
+ rule = rest.split()[0] if rest.split() else "UNKNOWN"
321
+ rule_counts[rule]["files"].add(filepath)
322
+ rule_counts[rule]["count"] += 1
323
+
324
+ if not rule_counts:
325
+ return ShellCommandOutput(
326
+ success=True,
327
+ command="golangci-lint",
328
+ stdout=stdout.strip() or "No issues found",
329
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
330
+ exit_code=0,
331
+ execution_time=None,
332
+ )
333
+
334
+ parts: list[str] = []
335
+ for rule in sorted(rule_counts.keys()):
336
+ info = rule_counts[rule]
337
+ parts.append(f"{rule}: {len(info['files'])} files, {info['count']} occurrences")
338
+ if verbosity >= VerbosityLevel.VERBOSE:
339
+ for fp in sorted(info["files"])[:10]:
340
+ parts.append(f" {fp}")
341
+ if len(info["files"]) > 10:
342
+ parts.append(f" ... and {len(info['files']) - 10} more files")
343
+
344
+ return ShellCommandOutput(
345
+ success=True,
346
+ command="golangci-lint",
347
+ stdout="\n".join(parts),
348
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
349
+ exit_code=0,
350
+ execution_time=None,
351
+ )
352
+
353
+
354
+ def _compress_golangci_json(
355
+ data: dict[str, Any],
356
+ stderr: str,
357
+ verbosity: VerbosityLevel,
358
+ ) -> ShellCommandOutput:
359
+ """Compress golangci-lint JSON output.
360
+
361
+ Args:
362
+ data: Parsed golangci-lint JSON.
363
+ stderr: Raw stderr.
364
+ verbosity: Current verbosity level.
365
+
366
+ Returns:
367
+ Compressed :class:`ShellCommandOutput`.
368
+ """
369
+ issues = data.get("Issues", [])
370
+ rule_counts = defaultdict(lambda: {"files": set(), "count": 0})
371
+
372
+ cdef dict issue
373
+ cdef str rule
374
+ cdef str filepath
375
+
376
+ for issue in issues:
377
+ rule = issue.get("FromLinter", "UNKNOWN")
378
+ filepath = issue.get("Pos", {}).get("Filename", "unknown")
379
+ rule_counts[rule]["files"].add(filepath)
380
+ rule_counts[rule]["count"] += 1
381
+
382
+ parts: list[str] = []
383
+ for rule in sorted(rule_counts.keys()):
384
+ info = rule_counts[rule]
385
+ parts.append(f"{rule}: {len(info['files'])} files, {info['count']} occurrences")
386
+
387
+ return ShellCommandOutput(
388
+ success=len(issues) == 0,
389
+ command="golangci-lint",
390
+ stdout="\n".join(parts) if parts else "No issues found",
391
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
392
+ exit_code=0 if not issues else 1,
393
+ execution_time=None,
394
+ )
395
+
396
+
397
+ def compress_grep(
398
+ stdout: str,
399
+ stderr: str,
400
+ verbosity: VerbosityLevel,
401
+ ) -> ShellCommandOutput:
402
+ """Compress grep/rg output by grouping matches per file.
403
+
404
+ Args:
405
+ stdout: Raw stdout.
406
+ stderr: Raw stderr.
407
+ verbosity: Current verbosity level.
408
+
409
+ Returns:
410
+ Compressed :class:`ShellCommandOutput`.
411
+ """
412
+ lines = stdout.splitlines()
413
+ file_lines = defaultdict(list)
414
+
415
+ cdef str line
416
+ cdef str stripped
417
+ cdef list parts
418
+ cdef str filepath
419
+ cdef str rest
420
+
421
+ # Pattern: file:line:match or file:match
422
+ for line in lines:
423
+ stripped = line.rstrip("\r")
424
+ if ":" in stripped:
425
+ parts = stripped.split(":", 2)
426
+ if len(parts) >= 2 and parts[0]:
427
+ filepath = parts[0]
428
+ rest = ":".join(parts[1:])
429
+ file_lines[filepath].append(rest)
430
+ else:
431
+ file_lines[""].append(stripped)
432
+ else:
433
+ file_lines[""].append(stripped)
434
+
435
+ if not file_lines:
436
+ return ShellCommandOutput(
437
+ success=True,
438
+ command="grep",
439
+ stdout=stdout.strip() or "No matches",
440
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
441
+ exit_code=0,
442
+ execution_time=None,
443
+ )
444
+
445
+ parts: list[str] = []
446
+ for filepath in sorted(file_lines.keys()):
447
+ matches = file_lines[filepath]
448
+ if filepath:
449
+ parts.append(f"{filepath}: {len(matches)} matches")
450
+ if verbosity >= VerbosityLevel.VERBOSE:
451
+ for m in matches[:5]:
452
+ parts.append(f" {m}")
453
+ if len(matches) > 5:
454
+ parts.append(f" ... {len(matches) - 5} more")
455
+ else:
456
+ parts.extend(matches[:10])
457
+ if len(matches) > 10:
458
+ parts.append(f"... {len(matches) - 10} more")
459
+
460
+ return ShellCommandOutput(
461
+ success=True,
462
+ command="grep",
463
+ stdout="\n".join(parts),
464
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
465
+ exit_code=0,
466
+ execution_time=None,
467
+ )
468
+
469
+
470
+ def compress_find(
471
+ stdout: str,
472
+ stderr: str,
473
+ verbosity: VerbosityLevel,
474
+ ) -> ShellCommandOutput:
475
+ """Compress find output by grouping results per directory.
476
+
477
+ Args:
478
+ stdout: Raw stdout.
479
+ stderr: Raw stderr.
480
+ verbosity: Current verbosity level.
481
+
482
+ Returns:
483
+ Compressed :class:`ShellCommandOutput`.
484
+ """
485
+ lines = stdout.splitlines()
486
+ dir_counts = defaultdict(lambda: {"files": 0, "dirs": 0})
487
+
488
+ cdef str line
489
+ cdef str stripped
490
+ cdef str directory
491
+
492
+ for line in lines:
493
+ stripped = line.rstrip("\r")
494
+ if not stripped:
495
+ continue
496
+ directory = stripped.rsplit("/", 1)[0] if "/" in stripped else "."
497
+ # Heuristic: trailing slash indicates directory
498
+ if stripped.endswith("/"):
499
+ dir_counts[directory]["dirs"] += 1
500
+ else:
501
+ dir_counts[directory]["files"] += 1
502
+
503
+ if not dir_counts:
504
+ return ShellCommandOutput(
505
+ success=True,
506
+ command="find",
507
+ stdout=stdout.strip() or "No results",
508
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
509
+ exit_code=0,
510
+ execution_time=None,
511
+ )
512
+
513
+ parts: list[str] = []
514
+ for directory in sorted(dir_counts.keys()):
515
+ counts = dir_counts[directory]
516
+ parts.append(f"{directory}: {counts['files']} files, {counts['dirs']} dirs")
517
+ if verbosity >= VerbosityLevel.VERBOSE:
518
+ # Show a few sample entries for this directory
519
+ samples = [line for line in lines if line.startswith(directory + "/")][:5]
520
+ for s in samples:
521
+ parts.append(f" {s.split('/')[-1]}")
522
+
523
+ return ShellCommandOutput(
524
+ success=True,
525
+ command="find",
526
+ stdout="\n".join(parts),
527
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
528
+ exit_code=0,
529
+ execution_time=None,
530
+ )
531
+
532
+
533
+ _COMMAND_HANDLERS: dict[str, Any] = {
534
+ "ruff": compress_ruff,
535
+ "eslint": compress_eslint,
536
+ "golangci-lint": compress_golangci,
537
+ "grep": compress_grep,
538
+ "rg": compress_grep,
539
+ "find": compress_find,
540
+ }
541
+
542
+
543
+ def _resolve_handler(command: str) -> Any:
544
+ cdef str token
545
+ cdef str name
546
+ cdef object handler
547
+
548
+ for token in command.strip().split():
549
+ name = token.rsplit("/", 1)[-1]
550
+ handler = _COMMAND_HANDLERS.get(name)
551
+ if handler is not None:
552
+ return handler
553
+ return None
554
+
555
+
556
+ def compress_lint(
557
+ command: str,
558
+ stdout: str,
559
+ stderr: str,
560
+ exit_code: int,
561
+ verbosity: VerbosityLevel,
562
+ ) -> ShellCommandOutput | None:
563
+ """Main dispatcher for lint compression strategies.
564
+
565
+ Args:
566
+ command: The original lint command.
567
+ stdout: Raw stdout.
568
+ stderr: Raw stderr.
569
+ exit_code: Process exit code.
570
+ verbosity: Current verbosity level.
571
+
572
+ Returns:
573
+ A compressed :class:`ShellCommandOutput` or ``None``.
574
+ """
575
+ cdef object handler
576
+
577
+ handler = _resolve_handler(command)
578
+ if handler is not None:
579
+ return handler(stdout, stderr, verbosity)
580
+
581
+ # Generic fallback: group by first token on each line that looks like a rule
582
+ lines = stdout.splitlines()
583
+ parts: list[str] = []
584
+
585
+ cdef str line
586
+ cdef str stripped_line
587
+ cdef object rule_match
588
+ cdef str key
589
+
590
+ if verbosity >= VerbosityLevel.VERBOSE:
591
+ groups = defaultdict(list)
592
+ for line in lines:
593
+ stripped_line = line.rstrip("\r")
594
+ rule_match = _RULE_PATTERN.search(stripped_line)
595
+ key = rule_match.group(1) if rule_match else "other"
596
+ groups[key].append(stripped_line)
597
+
598
+ for key in sorted(groups.keys()):
599
+ parts.append(f"{key}: {len(groups[key])} occurrences")
600
+ for line in groups[key][:3]:
601
+ parts.append(f" {line}")
602
+ else:
603
+ counts: dict[str, int] = {}
604
+ for line in lines:
605
+ stripped_line = line.rstrip("\r")
606
+ rule_match = _RULE_PATTERN.search(stripped_line)
607
+ key = rule_match.group(1) if rule_match else "other"
608
+ counts[key] = counts.get(key, 0) + 1
609
+
610
+ for key, count in sorted(counts.items()):
611
+ parts.append(f"{key}: {count} occurrences")
612
+
613
+ return ShellCommandOutput(
614
+ success=exit_code == 0,
615
+ command=command.strip(),
616
+ stdout="\n".join(parts) if parts else stdout[:512],
617
+ stderr=stderr if verbosity >= VerbosityLevel.VERY_VERBOSE else None,
618
+ exit_code=exit_code,
619
+ execution_time=None,
620
+ )
621
+
622
+
623
+ # ---------------------------------------------------------------------------
624
+ # Register with the strategy registry
625
+ # ---------------------------------------------------------------------------
626
+ get_registry().register("lint", compress_lint, priority=0)