ayder-cli 2.2.8__tar.gz → 2.3.0__tar.gz

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 (308) hide show
  1. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/.gitignore +0 -1
  2. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/PKG-INFO +34 -10
  3. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/README.md +31 -7
  4. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/docs/PROJECT_STRUCTURE.md +2 -1
  5. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/docs/config.toml.example +17 -4
  6. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/pyproject.toml +22 -3
  7. ayder_cli-2.3.0/scripts/check_exception_handlers.py +269 -0
  8. ayder_cli-2.3.0/scripts/check_log_arguments.py +1518 -0
  9. ayder_cli-2.3.0/scripts/check_logging_imports.py +111 -0
  10. ayder_cli-2.3.0/scripts/find_percent_logging.py +108 -0
  11. ayder_cli-2.3.0/scripts/log_argument_baseline.txt +279 -0
  12. ayder_cli-2.3.0/scripts/logging_gates.py +510 -0
  13. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/agents/callbacks.py +9 -9
  14. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/agents/registry.py +24 -11
  15. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/agents/runner.py +21 -16
  16. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/agents/tool.py +0 -3
  17. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/agents/worktree.py +7 -4
  18. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/application/execution_policy.py +5 -1
  19. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/application/runtime_factory.py +7 -1
  20. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/cli.py +75 -21
  21. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/cli_runner.py +24 -8
  22. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/core/cache_monitor.py +8 -4
  23. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/core/config.py +58 -18
  24. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/core/config_migration.py +25 -2
  25. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/core/default_context_manager.py +58 -7
  26. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/core/ollama_context_manager.py +48 -19
  27. ayder_cli-2.3.0/src/ayder_cli/diagnostics.py +197 -0
  28. ayder_cli-2.3.0/src/ayder_cli/log.py +44 -0
  29. ayder_cli-2.3.0/src/ayder_cli/logging_config.py +596 -0
  30. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/loops/chat_loop.py +339 -252
  31. ayder_cli-2.3.0/src/ayder_cli/masking.py +85 -0
  32. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/process_manager.py +9 -3
  33. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/prompts.py +3 -3
  34. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/claude.py +9 -7
  35. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/gemini.py +10 -7
  36. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/ollama.py +26 -15
  37. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/ollama_drivers/generic_xml.py +5 -4
  38. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/ollama_drivers/registry.py +11 -9
  39. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/ollama_inspector.py +4 -0
  40. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/openai.py +16 -11
  41. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/qwen.py +44 -20
  42. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/retry.py +19 -14
  43. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/context.py +9 -6
  44. ayder_cli-2.3.0/src/ayder_cli/tools/builtins/filesystem.py +386 -0
  45. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/notes.py +7 -5
  46. ayder_cli-2.3.0/src/ayder_cli/tools/builtins/search.py +533 -0
  47. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/search_definitions.py +20 -8
  48. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/shell.py +4 -1
  49. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/skill.py +7 -4
  50. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/task_tool.py +4 -1
  51. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/tasks.py +15 -9
  52. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/utils_tools.py +7 -5
  53. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/web.py +4 -1
  54. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/definition.py +15 -11
  55. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/execution.py +8 -4
  56. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/hooks.py +12 -9
  57. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/plugin_github.py +5 -4
  58. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/plugin_manager.py +18 -14
  59. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/registry.py +5 -5
  60. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/utils.py +12 -11
  61. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tui/__init__.py +10 -1
  62. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tui/app.py +116 -30
  63. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tui/commands.py +51 -22
  64. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tui/screens.py +29 -4
  65. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tui/widgets.py +24 -7
  66. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/ui.py +15 -3
  67. ayder_cli-2.3.0/tests/agents/test_agent_severity.py +106 -0
  68. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/agents/test_runner.py +61 -3
  69. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/agents/test_worktree_helpers.py +29 -0
  70. ayder_cli-2.3.0/tests/application/test_execution_result_truthfulness.py +124 -0
  71. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/application/test_runtime_factory.py +27 -0
  72. ayder_cli-2.3.0/tests/client/test_main.py +132 -0
  73. ayder_cli-2.3.0/tests/conftest.py +103 -0
  74. ayder_cli-2.3.0/tests/core/test_asyncio_bridge.py +597 -0
  75. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_config_coverage.py +4 -4
  76. ayder_cli-2.3.0/tests/core/test_config_provider_logging.py +54 -0
  77. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_default_context_manager.py +107 -13
  78. ayder_cli-2.3.0/tests/core/test_event_emission.py +492 -0
  79. ayder_cli-2.3.0/tests/core/test_event_stream.py +62 -0
  80. ayder_cli-2.3.0/tests/core/test_exception_guarantee.py +26 -0
  81. ayder_cli-2.3.0/tests/core/test_exception_hooks.py +470 -0
  82. ayder_cli-2.3.0/tests/core/test_log_facade.py +63 -0
  83. ayder_cli-2.3.0/tests/core/test_logging_config_keys.py +70 -0
  84. ayder_cli-2.3.0/tests/core/test_logging_sinks.py +189 -0
  85. ayder_cli-2.3.0/tests/core/test_loguru_caplog.py +92 -0
  86. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_ollama_context_manager.py +5 -7
  87. ayder_cli-2.3.0/tests/core/test_session_id_threading.py +300 -0
  88. ayder_cli-2.3.0/tests/core/test_sink_masking.py +821 -0
  89. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/loops/test_chat_loop_tool_truncation.py +112 -3
  90. ayder_cli-2.3.0/tests/loops/test_loop_severity.py +130 -0
  91. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/ollama_drivers/test_fallback.py +16 -2
  92. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/test_ollama_native.py +24 -0
  93. ayder_cli-2.3.0/tests/providers/test_provider_channel_binding.py +42 -0
  94. ayder_cli-2.3.0/tests/providers/test_provider_severity.py +101 -0
  95. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/test_qwen_async.py +57 -0
  96. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/test_retrying_provider.py +31 -4
  97. ayder_cli-2.3.0/tests/scripts/test_exception_gate.py +339 -0
  98. ayder_cli-2.3.0/tests/scripts/test_log_argument_gate.py +1747 -0
  99. ayder_cli-2.3.0/tests/scripts/test_logging_policy_gates.py +276 -0
  100. ayder_cli-2.3.0/tests/scripts/test_loguru_private_shape.py +304 -0
  101. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/test_cli.py +262 -15
  102. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/test_context.py +49 -2
  103. ayder_cli-2.3.0/tests/test_logging_config.py +88 -0
  104. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/test_tasks.py +37 -0
  105. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_bash_tool.py +10 -6
  106. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_definition_discovery.py +14 -3
  107. ayder_cli-2.3.0/tests/tools/test_file_editor_hardening.py +155 -0
  108. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_impl.py +15 -7
  109. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_impl_coverage.py +4 -2
  110. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_plugin_manager.py +37 -6
  111. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_search_codebase.py +24 -2
  112. ayder_cli-2.3.0/tests/tools/test_search_hardening.py +381 -0
  113. ayder_cli-2.3.0/tests/tools/test_tool_severity.py +109 -0
  114. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_utils.py +56 -3
  115. ayder_cli-2.3.0/tests/tui/__init__.py +1 -0
  116. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_input_box.py +4 -1
  117. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_confirm_screen.py +2 -0
  118. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_tui_commands_logging.py +37 -3
  119. ayder_cli-2.3.0/uv.lock +2443 -0
  120. ayder_cli-2.2.8/src/ayder_cli/logging_config.py +0 -115
  121. ayder_cli-2.2.8/src/ayder_cli/tools/builtins/filesystem.py +0 -306
  122. ayder_cli-2.2.8/src/ayder_cli/tools/builtins/search.py +0 -335
  123. ayder_cli-2.2.8/tests/client/test_main.py +0 -56
  124. ayder_cli-2.2.8/tests/core/test_config_provider_logging.py +0 -46
  125. ayder_cli-2.2.8/tests/test_logging_config.py +0 -82
  126. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/.github/workflows/ci.yml +0 -0
  127. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/.github/workflows/python-package.yml +0 -0
  128. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/.python-version +0 -0
  129. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/AGENTS.md +0 -0
  130. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/LICENSE +0 -0
  131. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/docs/README_AGENT_WORKFLOW.md +0 -0
  132. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/docs/README_OLLAMA.md +0 -0
  133. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/docs/cc.png +0 -0
  134. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/docs/superpowers/plans/2026-06-26-file-editor-improvements.md +0 -0
  135. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/docs/superpowers/plans/2026-06-26-session-save-resume.md +0 -0
  136. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/docs/superpowers/specs/2026-06-26-file-editor-improvements-design.md +0 -0
  137. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/docs/superpowers/specs/2026-06-26-session-save-resume-design.md +0 -0
  138. /ayder_cli-2.2.8/src/ayder_cli/providers/impl/ollama_drivers/__init__.py → /ayder_cli-2.3.0/scripts/exception_baseline.txt +0 -0
  139. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/__init__.py +0 -0
  140. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/__main__.py +0 -0
  141. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/agents/__init__.py +0 -0
  142. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/agents/config.py +0 -0
  143. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/agents/run.py +0 -0
  144. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/application/README.md +0 -0
  145. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/application/__init__.py +0 -0
  146. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/application/message_contract.py +0 -0
  147. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/application/validation.py +0 -0
  148. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/cli_callbacks.py +0 -0
  149. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/console.py +0 -0
  150. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/core/context.py +0 -0
  151. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/core/context_manager.py +0 -0
  152. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/core/context_manager_factory.py +0 -0
  153. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/core/result.py +0 -0
  154. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/core/session.py +0 -0
  155. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/loops/__init__.py +0 -0
  156. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/parser.py +0 -0
  157. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/__init__.py +0 -0
  158. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/base.py +0 -0
  159. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/deepseek.py +0 -0
  160. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/glm.py +0 -0
  161. {ayder_cli-2.2.8/tests/providers → ayder_cli-2.3.0/src/ayder_cli/providers/impl}/ollama_drivers/__init__.py +0 -0
  162. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/ollama_drivers/_errors.py +0 -0
  163. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/ollama_drivers/base.py +0 -0
  164. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/ollama_drivers/deepseek.py +0 -0
  165. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/ollama_drivers/generic_native.py +0 -0
  166. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/ollama_drivers/matrix.py +0 -0
  167. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/ollama_drivers/minimax.py +0 -0
  168. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/impl/ollama_drivers/qwen3.py +0 -0
  169. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/providers/orchestrator.py +0 -0
  170. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/services/__init__.py +0 -0
  171. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/services/interactions.py +0 -0
  172. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/themes/__init__.py +0 -0
  173. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/themes/ayder.py +0 -0
  174. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/themes/claude.py +0 -0
  175. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/__init__.py +0 -0
  176. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/__init__.py +0 -0
  177. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/background_process_tool.py +0 -0
  178. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/context_definitions.py +0 -0
  179. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/filesystem_definitions.py +0 -0
  180. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/note_tool.py +0 -0
  181. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/notes_definitions.py +0 -0
  182. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/process_manager_definitions.py +0 -0
  183. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/shell_definitions.py +0 -0
  184. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/skill_definitions.py +0 -0
  185. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/task_definitions.py +0 -0
  186. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/utils_tools_definitions.py +0 -0
  187. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/builtins/web_definitions.py +0 -0
  188. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/normalization.py +0 -0
  189. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/plugin_api.py +0 -0
  190. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/plugin_status.py +0 -0
  191. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tools/schemas.py +0 -0
  192. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tui/adapter.py +0 -0
  193. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tui/helpers.py +0 -0
  194. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tui/keybindings.py +0 -0
  195. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tui/parser.py +0 -0
  196. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tui/rendering.py +0 -0
  197. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tui/theme_manager.py +0 -0
  198. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/tui/types.py +0 -0
  199. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/src/ayder_cli/version.py +0 -0
  200. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/COVERAGE.md +0 -0
  201. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/__init__.py +0 -0
  202. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/agents/__init__.py +0 -0
  203. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/agents/test_agent_consolidated.py +0 -0
  204. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/agents/test_agent_tools.py +0 -0
  205. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/agents/test_callbacks.py +0 -0
  206. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/agents/test_config.py +0 -0
  207. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/agents/test_integration.py +0 -0
  208. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/agents/test_queue.py +0 -0
  209. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/agents/test_registry.py +0 -0
  210. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/agents/test_run.py +0 -0
  211. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/agents/test_runner_provider_unavailable.py +0 -0
  212. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/agents/test_tool.py +0 -0
  213. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/agents/test_worktree_isolation.py +0 -0
  214. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/application/test_message_contract.py +0 -0
  215. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/convergence/__init__.py +0 -0
  216. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/convergence/test_execution_policy_parity.py +0 -0
  217. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/convergence/test_runtime_wiring.py +0 -0
  218. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/convergence/test_validation_path.py +0 -0
  219. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_cache_monitor.py +0 -0
  220. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_config.py +0 -0
  221. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_config_migration.py +0 -0
  222. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_config_retry.py +0 -0
  223. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_config_v2.py +0 -0
  224. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_context_manager_factory.py +0 -0
  225. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_default_context_manager_should_compact.py +0 -0
  226. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_ollama_context_manager_cache_monitor.py +0 -0
  227. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_ollama_context_manager_max_history.py +0 -0
  228. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_parameter_aliasing.py +0 -0
  229. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_parser.py +0 -0
  230. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/core/test_session.py +0 -0
  231. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/loops/__init__.py +0 -0
  232. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/loops/test_base.py +0 -0
  233. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/loops/test_chat_loop_hook.py +0 -0
  234. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/__init__.py +0 -0
  235. {ayder_cli-2.2.8/tests/tui → ayder_cli-2.3.0/tests/providers/ollama_drivers}/__init__.py +0 -0
  236. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/ollama_drivers/test_base.py +0 -0
  237. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/ollama_drivers/test_deepseek.py +0 -0
  238. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/ollama_drivers/test_errors.py +0 -0
  239. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/ollama_drivers/test_generic_native.py +0 -0
  240. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/ollama_drivers/test_generic_xml.py +0 -0
  241. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/ollama_drivers/test_matrix.py +0 -0
  242. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/ollama_drivers/test_minimax.py +0 -0
  243. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/ollama_drivers/test_provider_integration.py +0 -0
  244. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/ollama_drivers/test_qwen3.py +0 -0
  245. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/ollama_drivers/test_registry.py +0 -0
  246. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/test_glm_async.py +0 -0
  247. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/test_ollama_inspector.py +0 -0
  248. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/test_ollama_xml.py +0 -0
  249. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/test_ollama_xml_autoroute.py +0 -0
  250. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/test_ollama_xml_usage.py +0 -0
  251. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/test_orchestrator_capabilities.py +0 -0
  252. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/test_retry_classifier.py +0 -0
  253. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/providers/test_retry_integration.py +0 -0
  254. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/services/test_boundary.py +0 -0
  255. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/services/test_interaction_sink.py +0 -0
  256. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/test_cli_callbacks.py +0 -0
  257. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/test_env_manager.py +0 -0
  258. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/test_notes.py +0 -0
  259. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/test_process_manager.py +0 -0
  260. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/__init__.py +0 -0
  261. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_agent_notes.py +0 -0
  262. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_background_process_tool.py +0 -0
  263. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_di_injection.py +0 -0
  264. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_note_tool.py +0 -0
  265. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_notes_api.py +0 -0
  266. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_path_security.py +0 -0
  267. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_plugin_api.py +0 -0
  268. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_plugin_github.py +0 -0
  269. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_plugin_loading.py +0 -0
  270. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_plugin_status.py +0 -0
  271. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_registry.py +0 -0
  272. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_registry_coverage.py +0 -0
  273. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_result.py +0 -0
  274. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_schemas.py +0 -0
  275. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_skill_tool.py +0 -0
  276. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_task_tool.py +0 -0
  277. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tools/test_web.py +0 -0
  278. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_agent_list_screen.py +0 -0
  279. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_do_clear_coordination.py +0 -0
  280. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_follow_mode.py +0 -0
  281. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_help_screen.py +0 -0
  282. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_keybindings.py +0 -0
  283. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_multi_select_screen.py +0 -0
  284. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_multiline_input.py +0 -0
  285. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_paste.py +0 -0
  286. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_pending_compact_consumer.py +0 -0
  287. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_pull_delivery.py +0 -0
  288. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_resume.py +0 -0
  289. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_status_bar_badges.py +0 -0
  290. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_themes.py +0 -0
  291. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_turn_consumer.py +0 -0
  292. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/tui/test_widgets.py +0 -0
  293. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_diff_preview.py +0 -0
  294. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_plugin_autoenable.py +0 -0
  295. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_provider_switch.py +0 -0
  296. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_run_tui_startup.py +0 -0
  297. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_tui_assistant_sanitization.py +0 -0
  298. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_tui_chat_loop.py +0 -0
  299. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_tui_commands_agent.py +0 -0
  300. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_tui_commands_plugin.py +0 -0
  301. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_tui_commands_provider.py +0 -0
  302. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_tui_commands_skill.py +0 -0
  303. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_tui_helpers.py +0 -0
  304. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_tui_temporal_command.py +0 -0
  305. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_tui_widgets.py +0 -0
  306. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_ui.py +0 -0
  307. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_ui_coverage.py +0 -0
  308. {ayder_cli-2.2.8 → ayder_cli-2.3.0}/tests/ui/test_ui_verbose.py +0 -0
@@ -24,7 +24,6 @@ htmlcov/
24
24
  .venv
25
25
  TODO
26
26
  .env
27
- uv.lock
28
27
 
29
28
  # Project Spesific
30
29
  .ayder
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: ayder-cli
3
- Version: 2.2.8
3
+ Version: 2.3.0
4
4
  Summary: AI agent for any LLMs
5
5
  Project-URL: Homepage, https://github.com/ayder/ayder-cli
6
6
  Project-URL: Repository, https://github.com/ayder/ayder-cli.git
@@ -15,7 +15,7 @@ Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Requires-Python: >=3.12
17
17
  Requires-Dist: httpx
18
- Requires-Dist: loguru
18
+ Requires-Dist: loguru<0.8,>=0.7
19
19
  Requires-Dist: ollama>=0.6.2
20
20
  Requires-Dist: openai
21
21
  Requires-Dist: python-dotenv>=1.0.0
@@ -209,10 +209,23 @@ tool_tags = ["core", "metadata"] # Enabled tool tags (see /plugin)
209
209
  agent_timeout = 300 # Seconds before a background agent is cancelled
210
210
 
211
211
  [logging]
212
- file_enabled = true
213
- file_path = ".ayder/log/ayder.log"
214
- rotation = "10 MB"
215
- retention = "7 days"
212
+ file_enabled = true # master switch for ALL file logging
213
+ file_path = ".ayder/log/ayder.log"
214
+ error_path = ".ayder/log/errors.log" # always written unless file_enabled = false
215
+ trace_enabled = false
216
+ trace_path = ".ayder/log/trace.jsonl"
217
+ rotation = "10 MB"
218
+ retention = "7 days"
219
+ # level = "INFO" # optional; omitted entirely when unset, which means NONE.
220
+ # NONE, ERROR, WARNING, INFO, DEBUG, TRACE
221
+
222
+ [logging.channels]
223
+ # Per-channel level overrides. Unlisted channels inherit the global level.
224
+ # Channels: llm, tool, agent, context, plugin, ui, core, external
225
+ # llm = "TRACE" # verbose LLM traffic
226
+ # plugin = "WARNING" # quiet plugin discovery
227
+ # external = "ERROR" # throttle third-party libraries
228
+ # ui = "NONE" # disable this channel entirely
216
229
 
217
230
  [context_manager]
218
231
  enabled = true
@@ -389,7 +402,7 @@ You can also manually manage context with the `context` tool and its slash-comma
389
402
  | `/notes` | Browse and edit markdown notes |
390
403
  | `/skill` | Activate a domain skill from `.ayder/skills/` via the shared `skill` tool backend |
391
404
  | `/verbose` | Toggle verbose mode |
392
- | `/logging` | Set log level for current session (NONE, ERROR, WARNING, INFO, DEBUG) |
405
+ | `/logging` | Set log level for current session (NONE, ERROR, WARNING, INFO, DEBUG, TRACE) |
393
406
  | `/compact` | Summarize conversation, save a context snapshot, clear, and reload |
394
407
  | `/save-context` | Summarize conversation and save it to a named context slot (no clear) |
395
408
  | `/load-context` | Load a saved context slot and restore it |
@@ -406,9 +419,20 @@ You can also manually manage context with the `context` tool and its slash-comma
406
419
  The LLM can also call `skill(action="list" | "load" | "unload")`
407
420
  automatically when a request clearly matches an available project skill.
408
421
 
409
- - Default: when logging is enabled, logs go to `.ayder/log/ayder.log` (not shown on screen).
422
+ - Errors are always captured to `.ayder/log/errors.log`, even when
423
+ `logging_level` is `NONE`. Set `file_enabled = false` under `[logging]` to
424
+ turn off all file logging including errors.
425
+ - Unhandled exceptions, SIGTERM and SIGINT are captured to
426
+ `.ayder/log/errors.log`. Records in flight are lost on `SIGKILL` (`kill -9`),
427
+ which no process can intercept.
428
+ - Narrative logs go to `.ayder/log/ayder.log` when a level is set.
429
+ - `--trace` writes structured events to `.ayder/log/trace.jsonl`. It is not a
430
+ log level.
431
+ - `--log-channel llm,tool` limits console and narrative output to those
432
+ subsystems. It never filters the error log.
410
433
  - TUI `/logging` changes are session-only and do not modify `config.toml`.
411
- - CLI `--verbose [LEVEL]` enables stdout logging for that run.
434
+ - CLI `--verbose` enables stdout logging for that run; it does not narrow
435
+ channels.
412
436
 
413
437
  ### Keyboard Shortcuts
414
438
 
@@ -166,10 +166,23 @@ tool_tags = ["core", "metadata"] # Enabled tool tags (see /plugin)
166
166
  agent_timeout = 300 # Seconds before a background agent is cancelled
167
167
 
168
168
  [logging]
169
- file_enabled = true
170
- file_path = ".ayder/log/ayder.log"
171
- rotation = "10 MB"
172
- retention = "7 days"
169
+ file_enabled = true # master switch for ALL file logging
170
+ file_path = ".ayder/log/ayder.log"
171
+ error_path = ".ayder/log/errors.log" # always written unless file_enabled = false
172
+ trace_enabled = false
173
+ trace_path = ".ayder/log/trace.jsonl"
174
+ rotation = "10 MB"
175
+ retention = "7 days"
176
+ # level = "INFO" # optional; omitted entirely when unset, which means NONE.
177
+ # NONE, ERROR, WARNING, INFO, DEBUG, TRACE
178
+
179
+ [logging.channels]
180
+ # Per-channel level overrides. Unlisted channels inherit the global level.
181
+ # Channels: llm, tool, agent, context, plugin, ui, core, external
182
+ # llm = "TRACE" # verbose LLM traffic
183
+ # plugin = "WARNING" # quiet plugin discovery
184
+ # external = "ERROR" # throttle third-party libraries
185
+ # ui = "NONE" # disable this channel entirely
173
186
 
174
187
  [context_manager]
175
188
  enabled = true
@@ -346,7 +359,7 @@ You can also manually manage context with the `context` tool and its slash-comma
346
359
  | `/notes` | Browse and edit markdown notes |
347
360
  | `/skill` | Activate a domain skill from `.ayder/skills/` via the shared `skill` tool backend |
348
361
  | `/verbose` | Toggle verbose mode |
349
- | `/logging` | Set log level for current session (NONE, ERROR, WARNING, INFO, DEBUG) |
362
+ | `/logging` | Set log level for current session (NONE, ERROR, WARNING, INFO, DEBUG, TRACE) |
350
363
  | `/compact` | Summarize conversation, save a context snapshot, clear, and reload |
351
364
  | `/save-context` | Summarize conversation and save it to a named context slot (no clear) |
352
365
  | `/load-context` | Load a saved context slot and restore it |
@@ -363,9 +376,20 @@ You can also manually manage context with the `context` tool and its slash-comma
363
376
  The LLM can also call `skill(action="list" | "load" | "unload")`
364
377
  automatically when a request clearly matches an available project skill.
365
378
 
366
- - Default: when logging is enabled, logs go to `.ayder/log/ayder.log` (not shown on screen).
379
+ - Errors are always captured to `.ayder/log/errors.log`, even when
380
+ `logging_level` is `NONE`. Set `file_enabled = false` under `[logging]` to
381
+ turn off all file logging including errors.
382
+ - Unhandled exceptions, SIGTERM and SIGINT are captured to
383
+ `.ayder/log/errors.log`. Records in flight are lost on `SIGKILL` (`kill -9`),
384
+ which no process can intercept.
385
+ - Narrative logs go to `.ayder/log/ayder.log` when a level is set.
386
+ - `--trace` writes structured events to `.ayder/log/trace.jsonl`. It is not a
387
+ log level.
388
+ - `--log-channel llm,tool` limits console and narrative output to those
389
+ subsystems. It never filters the error log.
367
390
  - TUI `/logging` changes are session-only and do not modify `config.toml`.
368
- - CLI `--verbose [LEVEL]` enables stdout logging for that run.
391
+ - CLI `--verbose` enables stdout logging for that run; it does not narrow
392
+ channels.
369
393
 
370
394
  ### Keyboard Shortcuts
371
395
 
@@ -160,7 +160,8 @@ cli.py:main()
160
160
  | `core/context_manager_factory.py` | Registry-based factory (OCP) | `ContextManagerFactory`, `context_manager_factory` |
161
161
  | `core/cache_monitor.py` | Timing-based KV-cache hit detection | `CacheMonitor`, `CacheStatus`, `CacheSample` |
162
162
  | `console.py` | Rich console singleton | `console` |
163
- | `logging_config.py` | Logger setup (loguru + stdlib bridge) | `setup_logging()` |
163
+ | `log.py` | Channel-scoped loguru facade; leaf module so `logging_config.py` and `core/config.py` can both depend on it | `get_logger()`, `LOG_LEVELS`, `CHANNELS`, `level_no()`, `flush()` |
164
+ | `logging_config.py` | Logger setup (loguru + stdlib bridge) | `setup_logging()`, `LoggingSettings` |
164
165
  | `version.py` | Package version constant | `__version__` |
165
166
 
166
167
  ### Root-Level Application Modules
@@ -117,10 +117,23 @@ theme = "ayder"
117
117
  palette = "auto"
118
118
 
119
119
  [logging]
120
- file_enabled = true
121
- file_path = ".ayder/log/ayder.log"
122
- rotation = "10 MB"
123
- retention = "7 days"
120
+ file_enabled = true # master switch for ALL file logging
121
+ file_path = ".ayder/log/ayder.log"
122
+ error_path = ".ayder/log/errors.log" # always written unless file_enabled = false
123
+ trace_enabled = false
124
+ trace_path = ".ayder/log/trace.jsonl"
125
+ rotation = "10 MB"
126
+ retention = "7 days"
127
+ # level = "INFO" # optional; omitted entirely when unset, which means NONE.
128
+ # NONE, ERROR, WARNING, INFO, DEBUG, TRACE
129
+
130
+ [logging.channels]
131
+ # Per-channel level overrides. Unlisted channels inherit the global level.
132
+ # Channels: llm, tool, agent, context, plugin, ui, core, external
133
+ # llm = "TRACE" # verbose LLM traffic
134
+ # plugin = "WARNING" # quiet plugin discovery
135
+ # external = "ERROR" # throttle third-party libraries
136
+ # ui = "NONE" # disable this channel entirely
124
137
 
125
138
  [context_manager]
126
139
  enabled = true
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ayder-cli"
7
- version = "2.2.8"
7
+ version = "2.3.0"
8
8
  description = "AI agent for any LLMs"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -22,7 +22,9 @@ classifiers = [
22
22
  dependencies = [
23
23
  "openai",
24
24
  "ollama>=0.6.2",
25
- "loguru",
25
+ # Bounded deliberately: the masking sinks wrap Loguru's private FileSink /
26
+ # Message / StreamSink shapes (§F5-R6), guarded by `_shape_guard()`.
27
+ "loguru>=0.7,<0.8",
26
28
  "httpx",
27
29
  "rich>=13.0.0",
28
30
  # Pinned exactly to match the runtime: 8.2.5+ replaced the single
@@ -85,7 +87,16 @@ lint = "ruff check src/"
85
87
  lint-fix = "ruff check src/ --fix"
86
88
  typecheck = "mypy src/"
87
89
  format = "ruff format src/"
88
- check-all = ["lint", "typecheck", "test"]
90
+ check-all = ["lint", "typecheck", "gates", "test"]
91
+
92
+ [tool.poe.tasks.gates]
93
+ help = "Run the logging AST gates"
94
+ sequence = [
95
+ { cmd = "python scripts/find_percent_logging.py" },
96
+ { cmd = "python scripts/check_logging_imports.py" },
97
+ { cmd = "python scripts/check_exception_handlers.py" },
98
+ { cmd = "python scripts/check_log_arguments.py" },
99
+ ]
89
100
 
90
101
  [tool.poe.tasks.clean]
91
102
  help = "Deep clean cache and build artifacts"
@@ -113,3 +124,11 @@ ignore_missing_imports = false
113
124
  [[tool.mypy.overrides]]
114
125
  module = ["zhipuai", "dashscope"]
115
126
  ignore_missing_imports = true
127
+
128
+ [tool.ruff.lint]
129
+ # Explicit rule set: ruff 0.16 widened its defaults from 59 to 413 rules,
130
+ # so an implicit default + extend-select drifts with the installed version.
131
+ select = ["E4", "E7", "E9", "F", "TRY400", "TRY401", "BLE001", "LOG", "G"]
132
+
133
+ [tool.ruff.lint.per-file-ignores]
134
+ "tests/**/*.py" = ["BLE001", "G004"]
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env python3
2
+ """Gate: every broad exception handler must satisfy the logging policy.
3
+
4
+ Verdicts:
5
+ PASS - a reachable satisfying statement was found
6
+ FAIL - swallows silently, or exits without satisfying
7
+ REPORT - branches before satisfying; needs a human
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import ast
13
+ import pathlib
14
+ import sys
15
+
16
+ LEVELS = {"trace", "debug", "info", "success", "warning", "error",
17
+ "exception", "critical"}
18
+ BROAD = {"Exception", "BaseException"}
19
+
20
+ # Resolved from this file, not the cwd: scripts/<this>.py -> repo root.
21
+ REPO = pathlib.Path(__file__).resolve().parent.parent
22
+ SRC_ROOT = REPO / "src" / "ayder_cli"
23
+ DEFAULT_BASELINE = REPO / "scripts" / "exception_baseline.txt"
24
+
25
+ # Frozen census (§C10). A different number means the gate is looking at the
26
+ # wrong tree, or the census moved without the plan being updated.
27
+ EXPECTED_BROAD = 131
28
+ BRANCHING = (ast.If, ast.Try, ast.For, ast.While, ast.With, ast.Match)
29
+ TERMINAL = (ast.Return, ast.Break, ast.Continue, ast.Pass)
30
+
31
+
32
+ def _is_broad(handler: ast.ExceptHandler) -> bool:
33
+ """Bare `except:` is NOT in scope — see CONTRACTS C10."""
34
+ t = handler.type
35
+ if t is None:
36
+ return False
37
+ if isinstance(t, ast.Name):
38
+ return t.id in BROAD
39
+ if isinstance(t, ast.Tuple):
40
+ return any(isinstance(e, ast.Name) and e.id in BROAD for e in t.elts)
41
+ return False
42
+
43
+
44
+ LOGGERS = {"logger", "llm_log", "tool_log"}
45
+
46
+
47
+ def _approved_receiver(node: ast.expr) -> bool:
48
+ """Only the §C8 bindings satisfy the policy.
49
+
50
+ Without this, any object's `.exception()`/`.opt()` would pass — a Future's
51
+ `.exception()` being the obvious trap.
52
+ """
53
+ return isinstance(node, ast.Name) and node.id in LOGGERS
54
+
55
+
56
+ def _is_exception_log(node: ast.stmt) -> bool:
57
+ """Either logger.opt(exception=True).<level>(...) or logger.exception(...)."""
58
+ if not isinstance(node, ast.Expr) or not isinstance(node.value, ast.Call):
59
+ return False
60
+ call = node.value
61
+ if not isinstance(call.func, ast.Attribute):
62
+ return False
63
+
64
+ # Form 2: logger.exception(...) — loguru shorthand, carries the exception.
65
+ # ruff TRY400 actively pushes code toward this, so it MUST be accepted —
66
+ # but only on a real logger. `future.exception()` must not satisfy policy.
67
+ if call.func.attr == "exception":
68
+ return _approved_receiver(call.func.value)
69
+
70
+ # Form 1: logger.opt(exception=True).<level>(...)
71
+ if call.func.attr not in LEVELS:
72
+ return False
73
+ recv = call.func.value
74
+ if not (isinstance(recv, ast.Call) and isinstance(recv.func, ast.Attribute)
75
+ and recv.func.attr == "opt"
76
+ and _approved_receiver(recv.func.value)):
77
+ return False
78
+ return any(kw.arg == "exception" and getattr(kw.value, "value", None) is True
79
+ for kw in recv.keywords)
80
+
81
+
82
+ def _is_failure_return(node: ast.stmt) -> bool:
83
+ """ToolError(...) or ExecutionResult(success=False, ...) — nothing else."""
84
+ if not isinstance(node, ast.Return) or node.value is None:
85
+ return False
86
+ v = node.value
87
+ if not isinstance(v, ast.Call):
88
+ return False
89
+ name = getattr(v.func, "id", None) or getattr(v.func, "attr", None)
90
+
91
+ if name == "ToolError":
92
+ return True
93
+ if name == "ExecutionResult":
94
+ # success=True is a SUCCESS return and satisfies nothing.
95
+ return any(kw.arg == "success" and getattr(kw.value, "value", None) is False
96
+ for kw in v.keywords)
97
+ return False
98
+
99
+
100
+ def _satisfies(node: ast.stmt) -> bool:
101
+ return (isinstance(node, ast.Raise)
102
+ or _is_failure_return(node)
103
+ or _is_exception_log(node))
104
+
105
+
106
+ def _has_reasoned_marker(line: str) -> bool:
107
+ """`# noqa: AYDER-EXC <reason>` — the reason text is mandatory.
108
+
109
+ A bare marker records no decision, so it must not suppress.
110
+ """
111
+ idx = line.find("AYDER-EXC")
112
+ if idx == -1:
113
+ return False
114
+ return bool(line[idx + len("AYDER-EXC"):].strip(" :-\t"))
115
+
116
+
117
+ def _verdict(handler: ast.ExceptHandler) -> str:
118
+ for stmt in handler.body:
119
+ if _satisfies(stmt):
120
+ return "PASS"
121
+ if isinstance(stmt, BRANCHING):
122
+ return "REPORT"
123
+ if isinstance(stmt, TERMINAL):
124
+ return "FAIL" # exits without satisfying
125
+ return "FAIL" # fell off the end
126
+
127
+
128
+ def _broad_handlers(path: str, tree: ast.AST) -> list[tuple[ast.ExceptHandler, str]]:
129
+ """Yield (handler, stable_id), id = `path::qualname::ordinal`.
130
+
131
+ NEVER key a finding by line number. Step 3-02 inserts an import into 15
132
+ files and every handler below it shifts; each remediation shifts the rest
133
+ again. A line-keyed baseline reports every untouched handler as a new
134
+ finding on the very next commit. This id survives both — verified: 119
135
+ handlers, 119 unique ids, byte-identical after prepending imports and
136
+ after editing a handler body.
137
+ """
138
+ out: list[tuple[ast.ExceptHandler, str]] = []
139
+ stack: list[str] = []
140
+ counter: dict[str, int] = {}
141
+
142
+ class _V(ast.NodeVisitor):
143
+ def _scope(self, node):
144
+ stack.append(node.name)
145
+ self.generic_visit(node)
146
+ stack.pop()
147
+
148
+ visit_FunctionDef = visit_AsyncFunctionDef = visit_ClassDef = _scope
149
+
150
+ def visit_Try(self, node):
151
+ for h in node.handlers:
152
+ if _is_broad(h):
153
+ key = f"{path}::{'.'.join(stack) or '<module>'}"
154
+ counter[key] = counter.get(key, 0) + 1
155
+ out.append((h, f"{key}::{counter[key]}"))
156
+ self.generic_visit(node)
157
+
158
+ # PEP 654 `except*` groups parse to a distinct node type (ast.TryStar,
159
+ # not ast.Try) with the same `.handlers` shape. Without this alias they
160
+ # are invisible to the census: generic_visit walks straight through
161
+ # into the ExceptHandler children and every `except* Exception:` is
162
+ # silently uncounted rather than silently failing — the gate would
163
+ # report success over a hole in its own coverage.
164
+ visit_TryStar = visit_Try
165
+
166
+ _V().visit(tree)
167
+ return out
168
+
169
+
170
+ def main() -> int:
171
+ ap = argparse.ArgumentParser()
172
+ ap.add_argument("--path", default=str(SRC_ROOT))
173
+ ap.add_argument("--baseline", default=str(DEFAULT_BASELINE))
174
+ ap.add_argument("--write-baseline", action="store_true")
175
+ ap.add_argument("--expect-broad", type=int, default=None,
176
+ help="fail unless exactly this many broad handlers are found")
177
+ args = ap.parse_args()
178
+
179
+ root = pathlib.Path(args.path)
180
+ if not root.is_dir():
181
+ print(f"ABORT: source root does not exist: {root}", file=sys.stderr)
182
+ return 2
183
+ py_files = sorted(root.rglob("*.py"))
184
+ if not py_files:
185
+ print(f"ABORT: no Python files under {root}", file=sys.stderr)
186
+ return 2
187
+
188
+ findings: list = []
189
+ problems: list[str] = []
190
+ broad_total = 0
191
+ examined = 0
192
+ for p in py_files:
193
+ text = p.read_text()
194
+ lines = text.splitlines()
195
+ try:
196
+ tree = ast.parse(text, filename=str(p))
197
+ except SyntaxError as e:
198
+ # Never skip silently — an unparseable file is a hole in coverage.
199
+ problems.append(f"UNPARSEABLE {p}: {e}")
200
+ continue
201
+ examined += 1
202
+ try:
203
+ # REPOSITORY-relative, so ids read `src/ayder_cli/...` exactly as
204
+ # they did before the root was resolved absolutely. The per-directory
205
+ # steps grep on that prefix; changing it would silently break them.
206
+ rel = str(p.resolve().relative_to(REPO))
207
+ except ValueError:
208
+ rel = str(p)
209
+ for node, stable_id in _broad_handlers(rel, tree):
210
+ broad_total += 1
211
+ line = lines[node.lineno - 1] if node.lineno <= len(lines) else ""
212
+ if _has_reasoned_marker(line):
213
+ continue
214
+ v = _verdict(node)
215
+ if v != "PASS":
216
+ # The KEY is verdict + stable id, and nothing else. The line
217
+ # number is carried alongside for humans and is never compared.
218
+ findings.append((f"{v} {stable_id}", f"{rel}:{node.lineno}"))
219
+
220
+ keys = {k for k, _ in findings}
221
+ where = dict(findings)
222
+
223
+ # Coverage summary on EVERY run, before any verdict is printed.
224
+ print(f"files={examined}/{len(py_files)} broad={broad_total} "
225
+ f"findings={len(keys)} root={root}")
226
+
227
+ if problems:
228
+ for x in problems:
229
+ print(f" {x}")
230
+ print(f"FAIL — {len(problems)} unparseable file(s)")
231
+ return 1
232
+
233
+ expect = args.expect_broad
234
+ if expect is None and root.resolve() == SRC_ROOT.resolve():
235
+ expect = EXPECTED_BROAD # full-tree run: census is frozen
236
+ if expect is not None and broad_total != expect:
237
+ print(f"FAIL — census: found {broad_total} broad handlers, expected {expect}")
238
+ return 1
239
+
240
+ if args.write_baseline:
241
+ pathlib.Path(args.baseline).write_text("\n".join(sorted(keys)) + "\n")
242
+ print(f"baseline written: {len(keys)} findings")
243
+ return 0
244
+
245
+ for k in sorted(keys):
246
+ print(f"{k}\t# {where[k]}") # location is display only
247
+
248
+ baseline = pathlib.Path(args.baseline)
249
+ if not baseline.exists():
250
+ print(f"\n{len(keys)} findings (no baseline)")
251
+ return 0
252
+
253
+ # PER-FINDING comparison on the stable id. A count check would let one
254
+ # finding vanish while a new one appears and still pass.
255
+ allowed = {line.split("\t")[0].strip() for line in baseline.read_text().splitlines()
256
+ if line.strip()}
257
+ new = sorted(keys - allowed)
258
+ if new:
259
+ print(f"\n{len(new)} NEW finding(s) not in baseline:")
260
+ for n in new:
261
+ print(f" {n}\t# {where[n]}")
262
+ return 1
263
+ fixed = len(allowed) - len(keys & allowed)
264
+ print(f"\n{len(keys)} findings, 0 new, {fixed} fixed since baseline")
265
+ return 0
266
+
267
+
268
+ if __name__ == "__main__":
269
+ sys.exit(main())