gdmcode 0.1.4__tar.gz → 0.1.5__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 (267) hide show
  1. {gdmcode-0.1.4 → gdmcode-0.1.5}/PKG-INFO +1 -1
  2. gdmcode-0.1.5/gdmcode/__init__.py +1 -0
  3. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/tool_orchestrator.py +3 -11
  4. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/cli.py +39 -5
  5. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/integrations/mcp_server.py +1 -1
  6. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/main.py +14 -0
  7. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/repl.py +79 -0
  8. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/__init__.py +15 -0
  9. {gdmcode-0.1.4 → gdmcode-0.1.5}/pyproject.toml +1 -1
  10. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_cli_smoke.py +37 -0
  11. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_repl_smoke.py +36 -0
  12. gdmcode-0.1.4/gdmcode/__init__.py +0 -1
  13. {gdmcode-0.1.4 → gdmcode-0.1.5}/.gitignore +0 -0
  14. {gdmcode-0.1.4 → gdmcode-0.1.5}/CONTRIBUTING.md +0 -0
  15. {gdmcode-0.1.4 → gdmcode-0.1.5}/README.md +0 -0
  16. {gdmcode-0.1.4 → gdmcode-0.1.5}/config.toml +0 -0
  17. {gdmcode-0.1.4 → gdmcode-0.1.5}/docs/agentic-runtime-audit.md +0 -0
  18. {gdmcode-0.1.4 → gdmcode-0.1.5}/docs/architecture.md +0 -0
  19. {gdmcode-0.1.4 → gdmcode-0.1.5}/docs/cli-reference.md +0 -0
  20. {gdmcode-0.1.4 → gdmcode-0.1.5}/docs/configuration.md +0 -0
  21. {gdmcode-0.1.4 → gdmcode-0.1.5}/docs/deployment.md +0 -0
  22. {gdmcode-0.1.4 → gdmcode-0.1.5}/docs/plugin-guide.md +0 -0
  23. {gdmcode-0.1.4 → gdmcode-0.1.5}/docs/quick-start.md +0 -0
  24. {gdmcode-0.1.4 → gdmcode-0.1.5}/docs/security-hardening.md +0 -0
  25. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/_internal/__init__.py +0 -0
  26. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/_internal/constants.py +0 -0
  27. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/_internal/domain_skills.py +0 -0
  28. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/__init__.py +0 -0
  29. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/commit_classifier.py +0 -0
  30. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/context_budget.py +0 -0
  31. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/daemon.py +0 -0
  32. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/dag_validator.py +0 -0
  33. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/debug_loop.py +0 -0
  34. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/impact_analyzer.py +0 -0
  35. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/impact_graph.py +0 -0
  36. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/loop.py +0 -0
  37. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/orchestrator.py +0 -0
  38. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/regression_guard.py +0 -0
  39. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/review_gate.py +0 -0
  40. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/risk_scorer.py +0 -0
  41. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/self_healing.py +0 -0
  42. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/smart_test_selector.py +0 -0
  43. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/system_prompt.py +0 -0
  44. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/task_tracker.py +0 -0
  45. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/test_validator.py +0 -0
  46. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/transcript.py +0 -0
  47. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/verification_loop.py +0 -0
  48. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/work_director.py +0 -0
  49. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/agent/worktree_manager.py +0 -0
  50. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/artifacts/__init__.py +0 -0
  51. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/artifacts/artifact_store.py +0 -0
  52. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/artifacts/verification_graph.py +0 -0
  53. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/auth.py +0 -0
  54. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/commands.py +0 -0
  55. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/config.py +0 -0
  56. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/cost_tracker.py +0 -0
  57. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/db/__init__.py +0 -0
  58. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/db/migrations.py +0 -0
  59. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/enterprise/__init__.py +0 -0
  60. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/enterprise/audit_log.py +0 -0
  61. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/enterprise/identity.py +0 -0
  62. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/enterprise/rbac.py +0 -0
  63. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/enterprise/team_config.py +0 -0
  64. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/enterprise/usage_analytics.py +0 -0
  65. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/exceptions.py +0 -0
  66. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/git_workflow.py +0 -0
  67. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/integrations/__init__.py +0 -0
  68. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/integrations/github_actions.py +0 -0
  69. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/integrations/sentry_integration.py +0 -0
  70. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/integrations/sentry_server.py +0 -0
  71. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/integrations/webhook_security.py +0 -0
  72. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/memory/__init__.py +0 -0
  73. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/memory/code_index.py +0 -0
  74. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/memory/compressor.py +0 -0
  75. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/memory/context_memory.py +0 -0
  76. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/memory/continuous_memory.py +0 -0
  77. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/memory/conventions.py +0 -0
  78. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/memory/db.py +0 -0
  79. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/memory/document_index.py +0 -0
  80. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/memory/file_cache.py +0 -0
  81. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/memory/project_scanner.py +0 -0
  82. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/memory/session_store.py +0 -0
  83. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/models/__init__.py +0 -0
  84. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/models/client.py +0 -0
  85. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/models/definitions.py +0 -0
  86. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/models/router.py +0 -0
  87. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/models/schemas.py +0 -0
  88. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/permissions.py +0 -0
  89. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/remote/__init__.py +0 -0
  90. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/remote/command_filter.py +0 -0
  91. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/remote/models.py +0 -0
  92. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/remote/permission_handler.py +0 -0
  93. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/remote/phone_ui.py +0 -0
  94. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/remote/protocol.py +0 -0
  95. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/remote/qr.py +0 -0
  96. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/remote/server.py +0 -0
  97. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/remote/token_manager.py +0 -0
  98. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/remote/tunnel.py +0 -0
  99. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/runtime/__init__.py +0 -0
  100. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/runtime/branch_farm.py +0 -0
  101. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/runtime/replay.py +0 -0
  102. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/sandbox/__init__.py +0 -0
  103. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/sandbox/hermetic.py +0 -0
  104. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/sandbox/policy.py +0 -0
  105. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/sdk/__init__.py +0 -0
  106. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/sdk/plugin_base.py +0 -0
  107. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/sdk/plugin_host.py +0 -0
  108. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/sdk/plugin_loader.py +0 -0
  109. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/security.py +0 -0
  110. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/server/__init__.py +0 -0
  111. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/server/bridge.py +0 -0
  112. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/server/bridge_cli.py +0 -0
  113. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/server/bridge_client.py +0 -0
  114. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/server/protocol_version.py +0 -0
  115. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/session/__init__.py +0 -0
  116. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/session/event_fanout.py +0 -0
  117. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/session/input_broker.py +0 -0
  118. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/session/permission_bridge.py +0 -0
  119. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/_atomic.py +0 -0
  120. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/agent_tools.py +0 -0
  121. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/ask_user_tool.py +0 -0
  122. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/bash_tool.py +0 -0
  123. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/browser_tool.py +0 -0
  124. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/browser_tools.py +0 -0
  125. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/dep_tools.py +0 -0
  126. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/document_reader.py +0 -0
  127. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/document_tool.py +0 -0
  128. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/document_writer.py +0 -0
  129. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/impact_tools.py +0 -0
  130. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/playwright_tool.py +0 -0
  131. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/quality_tools.py +0 -0
  132. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/read_tools.py +0 -0
  133. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/result_cache.py +0 -0
  134. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/search_tools.py +0 -0
  135. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/shell_tools.py +0 -0
  136. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/tools/write_tools.py +0 -0
  137. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/voice/__init__.py +0 -0
  138. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/voice/audio_capture.py +0 -0
  139. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/voice/audio_playback.py +0 -0
  140. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/voice/errors.py +0 -0
  141. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/voice/models.py +0 -0
  142. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/voice/providers.py +0 -0
  143. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/voice/vad.py +0 -0
  144. {gdmcode-0.1.4 → gdmcode-0.1.5}/gdmcode/voice/voice_loop.py +0 -0
  145. {gdmcode-0.1.4 → gdmcode-0.1.5}/proxy/Dockerfile +0 -0
  146. {gdmcode-0.1.4 → gdmcode-0.1.5}/proxy/main.py +0 -0
  147. {gdmcode-0.1.4 → gdmcode-0.1.5}/proxy/requirements.txt +0 -0
  148. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/__init__.py +0 -0
  149. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/remote/__init__.py +0 -0
  150. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/remote/test_remote_server.py +0 -0
  151. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_agent_loop.py +0 -0
  152. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_agent_tools.py +0 -0
  153. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_api_fallback.py +0 -0
  154. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_artifact_store.py +0 -0
  155. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_audit_log.py +0 -0
  156. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_auth.py +0 -0
  157. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_auto_quality.py +0 -0
  158. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_autonomy_levels.py +0 -0
  159. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_bash_tool.py +0 -0
  160. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_batch_api.py +0 -0
  161. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_branch_farm.py +0 -0
  162. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_bridge.py +0 -0
  163. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_bridge_smoke.py +0 -0
  164. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_browser_tool_smoke.py +0 -0
  165. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_browser_tools.py +0 -0
  166. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_btw_queue.py +0 -0
  167. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_budget_tracker.py +0 -0
  168. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_chrome_extension.py +0 -0
  169. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_ci_runner.py +0 -0
  170. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_code_index.py +0 -0
  171. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_commands.py +0 -0
  172. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_compression.py +0 -0
  173. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_confidence.py +0 -0
  174. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_config.py +0 -0
  175. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_continuous_memory.py +0 -0
  176. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_convention_drift.py +0 -0
  177. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_cost_tracker.py +0 -0
  178. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_daemon.py +0 -0
  179. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_daemon_stability.py +0 -0
  180. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_daemon_watchdog.py +0 -0
  181. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_db.py +0 -0
  182. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_debate.py +0 -0
  183. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_debug_loop.py +0 -0
  184. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_debug_loop_smoke.py +0 -0
  185. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_dep_tools.py +0 -0
  186. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_doctor.py +0 -0
  187. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_document_index.py +0 -0
  188. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_document_reader.py +0 -0
  189. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_document_tool.py +0 -0
  190. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_document_writer.py +0 -0
  191. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_domain_skills.py +0 -0
  192. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_eval_harness.py +0 -0
  193. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_event_log.py +0 -0
  194. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_failure_taxonomy.py +0 -0
  195. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_file_tools.py +0 -0
  196. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_git_workflow.py +0 -0
  197. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_github_actions.py +0 -0
  198. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_health.py +0 -0
  199. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_hermetic_sandbox.py +0 -0
  200. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_identity_rbac.py +0 -0
  201. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_impact_analysis.py +0 -0
  202. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_impact_analyzer.py +0 -0
  203. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_impact_graph.py +0 -0
  204. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_impact_tools.py +0 -0
  205. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_injection_gate.py +0 -0
  206. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_leaderboard.py +0 -0
  207. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_local_models.py +0 -0
  208. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_loop.py +0 -0
  209. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_loop_p3.py +0 -0
  210. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_mcp_server.py +0 -0
  211. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_memory.py +0 -0
  212. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_migrations.py +0 -0
  213. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_mock_provider.py +0 -0
  214. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_model_config.py +0 -0
  215. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_orchestrator.py +0 -0
  216. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_package.py +0 -0
  217. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_permissions.py +0 -0
  218. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_phase2_modules.py +0 -0
  219. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_phone_ui.py +0 -0
  220. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_playwright_tool.py +0 -0
  221. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_plugin_sdk.py +0 -0
  222. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_protocol_version.py +0 -0
  223. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_provenance.py +0 -0
  224. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_proxy_server.py +0 -0
  225. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_quality_integration.py +0 -0
  226. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_reasoning_toggle.py +0 -0
  227. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_redaction.py +0 -0
  228. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_regression_collector.py +0 -0
  229. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_regression_guard_integration.py +0 -0
  230. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_regression_runner.py +0 -0
  231. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_replay.py +0 -0
  232. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_resilience.py +0 -0
  233. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_result_cache.py +0 -0
  234. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_review_gate_expanded.py +0 -0
  235. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_risk_scorer.py +0 -0
  236. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_rollback.py +0 -0
  237. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_router.py +0 -0
  238. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_router_compressor_conventions.py +0 -0
  239. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_router_escalation.py +0 -0
  240. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_sandbox.py +0 -0
  241. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_scoring.py +0 -0
  242. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_search_tools.py +0 -0
  243. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_self_healing.py +0 -0
  244. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_semantic_edit.py +0 -0
  245. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_sentry_integration.py +0 -0
  246. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_session_checkpoint.py +0 -0
  247. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_session_controller.py +0 -0
  248. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_session_restore.py +0 -0
  249. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_signal_handling.py +0 -0
  250. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_swebench_adapter.py +0 -0
  251. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_swebench_runner.py +0 -0
  252. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_system_prompt.py +0 -0
  253. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_team_config.py +0 -0
  254. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_tool_cache.py +0 -0
  255. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_tool_orchestrator.py +0 -0
  256. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_tool_timeout.py +0 -0
  257. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_tools_registry.py +0 -0
  258. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_tunnel_qr.py +0 -0
  259. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_usage_analytics.py +0 -0
  260. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_verification_graph.py +0 -0
  261. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_verification_loop.py +0 -0
  262. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_voice_loop.py +0 -0
  263. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_voice_providers.py +0 -0
  264. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_whole_codebase.py +0 -0
  265. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/test_work_director.py +0 -0
  266. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/voice/__init__.py +0 -0
  267. {gdmcode-0.1.4 → gdmcode-0.1.5}/tests/voice/test_audio_foundation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gdmcode
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: gdm: AI coding agent for professional developers
5
5
  Project-URL: Homepage, https://github.com/guidegdm/gdmcode
6
6
  Project-URL: Repository, https://github.com/guidegdm/gdmcode
@@ -0,0 +1 @@
1
+ __version__ = "0.1.5"
@@ -25,7 +25,7 @@ from typing import Any
25
25
  from gdmcode._internal.constants import _ALWAYS_DENY_TOOLS, _WRITE_TOOLS
26
26
  from gdmcode.exceptions import ToolPermissionError
27
27
  from gdmcode.security import AuditLogger
28
- from gdmcode.tools import REGISTRY, ToolResult
28
+ from gdmcode.tools import REGISTRY, ToolResult, ensure_default_tools_registered
29
29
 
30
30
  __all__ = ["ToolOrchestrator"]
31
31
 
@@ -144,15 +144,7 @@ _TOOL_CACHE: _ToolResultCache = _ToolResultCache()
144
144
 
145
145
  def _ensure_tools_registered() -> None:
146
146
  """Import all tool modules so they register themselves with REGISTRY."""
147
- import gdmcode.tools.bash_tool # noqa: F401
148
- import gdmcode.tools.read_tools # noqa: F401
149
- import gdmcode.tools.search_tools # noqa: F401
150
- import gdmcode.tools.shell_tools # noqa: F401
151
- import gdmcode.tools.ask_user_tool # noqa: F401
152
- import gdmcode.tools.write_tools # noqa: F401
153
- import gdmcode.tools.dep_tools # noqa: F401
154
- import gdmcode.tools.impact_tools # noqa: F401
155
- import gdmcode.tools.browser_tool # noqa: F401
147
+ ensure_default_tools_registered()
156
148
 
157
149
 
158
150
  _ensure_tools_registered()
@@ -399,4 +391,4 @@ class ToolOrchestrator:
399
391
  """Dispatch a registered plugin tool by its namespaced tool_name."""
400
392
  if tool_name not in self._plugin_channels:
401
393
  raise KeyError(f"Unknown plugin tool: {tool_name}")
402
- return self._dispatch_plugin(tool_name, args)
394
+ return self._dispatch_plugin(tool_name, args)
@@ -287,7 +287,8 @@ def cmd_health(
287
287
  GdmDatabase = _GdmDatabase
288
288
  from gdmcode.models.definitions import PROVIDER_BASE_URLS, ModelTier, get_model
289
289
  if REGISTRY is None:
290
- from gdmcode.tools import REGISTRY as _REGISTRY
290
+ from gdmcode.tools import REGISTRY as _REGISTRY, ensure_default_tools_registered
291
+ ensure_default_tools_registered()
291
292
  REGISTRY = _REGISTRY
292
293
 
293
294
  @dataclass
@@ -352,8 +353,28 @@ def cmd_health(
352
353
  try:
353
354
  tools = REGISTRY.all_tools()
354
355
  count = len(tools)
355
- status = "ok" if count > 0 else "fail"
356
- return CheckResult("tools", status, latency_ms=int((time.monotonic() - t0) * 1000), detail=f"{count} tools")
356
+ if count == 0:
357
+ return CheckResult("tools", "fail", latency_ms=int((time.monotonic() - t0) * 1000), detail="0 tools registered")
358
+ probe_name = "powershell" if sys.platform == "win32" else "bash"
359
+ probe_args = (
360
+ {"command": "$PWD.Path", "timeout": 5}
361
+ if probe_name == "powershell"
362
+ else {"command": "pwd", "timeout": 5, "read_only": True}
363
+ )
364
+ probe = REGISTRY.call(probe_name, probe_args)
365
+ if not probe.ok:
366
+ return CheckResult(
367
+ "tools",
368
+ "fail",
369
+ latency_ms=int((time.monotonic() - t0) * 1000),
370
+ detail=f"{count} registered; {probe_name} probe failed: {probe.error}",
371
+ )
372
+ return CheckResult(
373
+ "tools",
374
+ "ok",
375
+ latency_ms=int((time.monotonic() - t0) * 1000),
376
+ detail=f"{count} registered; {probe_name} probe ok",
377
+ )
357
378
  except Exception as exc: # noqa: BLE001
358
379
  return CheckResult("tools", "fail", detail=str(exc))
359
380
 
@@ -398,7 +419,8 @@ def cmd_health(
398
419
  ]
399
420
 
400
421
  overall_fail = any(
401
- r.status in ("fail", "missing") and r.priority in ("P0", "P1")
422
+ r.status == "fail" and r.priority in ("P0", "P1")
423
+ or r.status == "missing" and r.priority == "P0"
402
424
  for r in all_checks
403
425
  )
404
426
 
@@ -1179,6 +1201,7 @@ def _run_one_shot(cfg: object, prompt: str, *, yes: bool, model_override: str |
1179
1201
  """
1180
1202
  from rich.status import Status
1181
1203
  from gdmcode.agent.loop import EventType
1204
+ from gdmcode.repl import _format_llm_error
1182
1205
 
1183
1206
  if not _has_model_connection(cfg):
1184
1207
  console.print("[red]No model connection configured.[/red]")
@@ -1248,10 +1271,21 @@ def _run_one_shot(cfg: object, prompt: str, *, yes: bool, model_override: str |
1248
1271
  console.print(event.content)
1249
1272
  elif event.type == EventType.ERROR:
1250
1273
  status.stop()
1251
- console.print(f"[red]Error:[/red] {event.content}")
1274
+ console.print(_format_llm_error(event.content))
1252
1275
  had_error = True
1253
1276
  elif event.type == EventType.TOOL_CALL:
1277
+ status.stop()
1254
1278
  console.print(f"[yellow] [tool] {event.tool_name}[/yellow]")
1279
+ elif event.type == EventType.TOOL_RESULT:
1280
+ status.stop()
1281
+ result = event.result
1282
+ if result is not None and getattr(result, "error", None):
1283
+ console.print(f"[red] [tool error] {event.tool_name}:[/red] {getattr(result, 'error')}")
1284
+ had_error = True
1285
+ elif result is not None:
1286
+ output = (getattr(result, "output", "") or "").strip()
1287
+ if output:
1288
+ console.print(f"[dim]{output[:2000]}[/dim]")
1255
1289
  elif event.type == EventType.DONE:
1256
1290
  status.stop()
1257
1291
  except Exception as exc: # noqa: BLE001
@@ -51,7 +51,7 @@ _INTERNAL_ERROR = -32603
51
51
  class MCPServer:
52
52
  """Minimal MCP server with stdio transport."""
53
53
 
54
- def __init__(self, name: str = "gdmcode", version: str = "0.1.4") -> None:
54
+ def __init__(self, name: str = "gdmcode", version: str = "0.1.5") -> None:
55
55
  self._name = name
56
56
  self._version = version
57
57
  self._tools: dict[str, MCPTool] = {}
@@ -6,6 +6,7 @@ pyproject.toml's `gdm = "gdmcode.main:main"` script works.
6
6
  from __future__ import annotations
7
7
 
8
8
  from importlib.metadata import version as _pkg_version, PackageNotFoundError
9
+ import sys
9
10
 
10
11
  from gdmcode.cli import app
11
12
 
@@ -20,8 +21,21 @@ def _get_version() -> str:
20
21
 
21
22
  def main() -> None:
22
23
  """Entry point for the `gdm` CLI command."""
24
+ _configure_stdio()
23
25
  app()
24
26
 
25
27
 
28
+ def _configure_stdio() -> None:
29
+ """Avoid Windows cp1252 crashes when Rich output is redirected."""
30
+ for stream_name in ("stdout", "stderr"):
31
+ stream = getattr(sys, stream_name, None)
32
+ if stream is None or not hasattr(stream, "reconfigure"):
33
+ continue
34
+ try:
35
+ stream.reconfigure(encoding="utf-8", errors="replace")
36
+ except (OSError, ValueError):
37
+ pass
38
+
39
+
26
40
  if __name__ == "__main__":
27
41
  main()
@@ -162,6 +162,35 @@ def _format_llm_error(error: object) -> str:
162
162
  return f"[red]{title}{code}.[/red]\n[dim]{escape(raw[:800])}[/dim]\n[yellow]{hint}[/yellow]"
163
163
 
164
164
 
165
+ def _is_connection_probe(text: str) -> bool:
166
+ """Return True for tiny greetings/checks that should not enter tool mode."""
167
+ import re
168
+
169
+ normalized = re.sub(r"[^a-z0-9\s']", " ", text.lower()).strip()
170
+ normalized = re.sub(r"\s+", " ", normalized)
171
+ if not normalized:
172
+ return False
173
+ direct = {
174
+ "hey",
175
+ "hi",
176
+ "hello",
177
+ "yo",
178
+ "ping",
179
+ "test",
180
+ "hello there",
181
+ "hey there",
182
+ "hi there",
183
+ "are you there",
184
+ "you there",
185
+ }
186
+ if normalized in direct:
187
+ return True
188
+ words = normalized.split()
189
+ return len(words) <= 4 and words[0] in {"hey", "hi", "hello"} and all(
190
+ word in {"hey", "hi", "hello", "there", "gdm", "agent", "please"} for word in words
191
+ )
192
+
193
+
165
194
  def _render_event(event: object, status: Status, console: Console) -> bool:
166
195
  """Render one AgentEvent to the terminal while the spinner is live."""
167
196
  from gdmcode.agent.loop import EventType # lazy
@@ -173,6 +202,7 @@ def _render_event(event: object, status: Status, console: Console) -> bool:
173
202
  label = f"[dim cyan]Thinking... {snippet}[/dim cyan]"
174
203
  status.update(label)
175
204
  case EventType.TOOL_CALL:
205
+ status.stop()
176
206
  console.print(f"[yellow][tool] {ev.tool_name}({_fmt_args(ev.args)})[/yellow]") # type: ignore[union-attr]
177
207
  case EventType.TOOL_RESULT:
178
208
  result_str = str(ev.result or "")[:80] # type: ignore[union-attr]
@@ -384,6 +414,51 @@ def start_repl(cfg: "GdmConfig", db: "GdmDatabase", *, yes: bool = False, model_
384
414
  console.print("[yellow]Agent loop not available in this installation.[/yellow]")
385
415
  return False
386
416
 
417
+ def _run_connection_probe(text: str) -> None:
418
+ """Send a tiny no-tools request to confirm the active LLM route."""
419
+ nonlocal cfg
420
+ if not _has_model_route():
421
+ try:
422
+ cfg = _reload_cfg(require_credentials=True) # type: ignore[assignment]
423
+ dispatcher._cfg = cfg # type: ignore[attr-defined]
424
+ dispatcher._provider = getattr(cfg, "provider", dispatcher._provider) # type: ignore[attr-defined]
425
+ except Exception as exc: # noqa: BLE001
426
+ _print_onboarding(console, error=str(exc))
427
+ return
428
+
429
+ status = Status("[cyan]Checking LLM connection...[/cyan]", console=console, spinner="dots")
430
+ status.start()
431
+ try:
432
+ from gdmcode.models.client import GdmClient
433
+ from gdmcode.models.definitions import ModelTier, get_model
434
+
435
+ tier = model_override or ModelTier.CODER
436
+ model_def = get_model(tier, cfg.provider)
437
+ proxy_active = bool(
438
+ getattr(cfg, "proxy_enabled", False)
439
+ and getattr(cfg, "proxy_url", "")
440
+ and getattr(cfg, "proxy_token", "")
441
+ )
442
+ client = (
443
+ GdmClient.for_proxy(getattr(cfg, "proxy_url"), getattr(cfg, "proxy_token"))
444
+ if proxy_active
445
+ else GdmClient(cfg)
446
+ )
447
+ response = client.complete_text(
448
+ text,
449
+ model=model_def.id,
450
+ max_tokens=80,
451
+ system=(
452
+ "You are gdm code's CLI connection check. Reply in one short, friendly "
453
+ "sentence. Confirm the LLM is connected. Do not inspect files or call tools."
454
+ ),
455
+ ).strip()
456
+ status.stop()
457
+ console.print(response or "[green]LLM connected.[/green]")
458
+ except Exception as exc: # noqa: BLE001
459
+ status.stop()
460
+ console.print(_format_llm_error(exc))
461
+
387
462
  if not getattr(cfg, "gdm_quiet", False):
388
463
  if _has_model_route():
389
464
  console.print("[dim]Agent will initialize on first prompt.[/dim]")
@@ -587,6 +662,10 @@ def start_repl(cfg: "GdmConfig", db: "GdmDatabase", *, yes: bool = False, model_
587
662
  console.print("[yellow]Restore not supported by current agent.[/yellow]")
588
663
  continue
589
664
 
665
+ if _is_connection_probe(text):
666
+ _run_connection_probe(text)
667
+ continue
668
+
590
669
  pending = _show_pending_btw(db, session_id, console)
591
670
  full_message = text
592
671
  if pending:
@@ -20,6 +20,7 @@ __all__ = [
20
20
  "ToolResult",
21
21
  "ToolRegistry",
22
22
  "REGISTRY",
23
+ "ensure_default_tools_registered",
23
24
  "BrowserTools",
24
25
  "BrowserToolResult",
25
26
  ]
@@ -155,6 +156,20 @@ class ToolRegistry:
155
156
  # Module-level singleton — all concrete tool modules import and extend this.
156
157
  REGISTRY: ToolRegistry = ToolRegistry()
157
158
 
159
+
160
+ def ensure_default_tools_registered() -> None:
161
+ """Import built-in tool modules so they register with REGISTRY."""
162
+ import gdmcode.tools.bash_tool # noqa: F401
163
+ import gdmcode.tools.read_tools # noqa: F401
164
+ import gdmcode.tools.search_tools # noqa: F401
165
+ import gdmcode.tools.shell_tools # noqa: F401
166
+ import gdmcode.tools.ask_user_tool # noqa: F401
167
+ import gdmcode.tools.write_tools # noqa: F401
168
+ import gdmcode.tools.dep_tools # noqa: F401
169
+ import gdmcode.tools.impact_tools # noqa: F401
170
+ import gdmcode.tools.browser_tool # noqa: F401
171
+
172
+
158
173
  # Re-export browser tools so callers can do `from gdmcode.tools import BrowserTools`.
159
174
  # Import after REGISTRY is defined to avoid circular imports.
160
175
  from gdmcode.tools.browser_tools import BrowserToolResult, BrowserTools, BrowserToolsSync # noqa: E402, F401
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "gdmcode"
7
- version = "0.1.4"
7
+ version = "0.1.5"
8
8
  description = "gdm: AI coding agent for professional developers"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1,6 +1,7 @@
1
1
  """Smoke tests for gdm CLI entrypoints."""
2
2
  from __future__ import annotations
3
3
 
4
+ from types import SimpleNamespace
4
5
  from unittest.mock import MagicMock, patch
5
6
 
6
7
  import pytest
@@ -57,3 +58,39 @@ class TestCliSmoke:
57
58
  def test_version_shows_version_string(self) -> None:
58
59
  result = runner.invoke(app, ["version"])
59
60
  assert "gdm" in result.output.lower() or "0." in result.output
61
+
62
+ def test_health_bootstraps_and_executes_tools(self, tmp_path, monkeypatch) -> None:
63
+ import gdmcode.cli as cli
64
+
65
+ class FakeDb:
66
+ def __init__(self, project_root):
67
+ self.project_root = project_root
68
+
69
+ def execute(self, *_args, **_kwargs):
70
+ return None
71
+
72
+ class FakeDaemon:
73
+ is_running = True
74
+
75
+ def __init__(self, db):
76
+ self.db = db
77
+
78
+ def pending_count(self):
79
+ return 0
80
+
81
+ monkeypatch.setattr(cli, "GdmDatabase", FakeDb)
82
+ monkeypatch.setattr(cli, "BackgroundDaemon", FakeDaemon)
83
+ monkeypatch.setattr(cli, "REGISTRY", None)
84
+ monkeypatch.setattr(
85
+ cli,
86
+ "load_config",
87
+ lambda: SimpleNamespace(provider="gemini", api_key="key", project_root=tmp_path),
88
+ )
89
+
90
+ response = SimpleNamespace(status_code=200)
91
+ with patch("gdmcode.cli.httpx.get", return_value=response):
92
+ result = runner.invoke(app, ["health"])
93
+
94
+ assert "tools" in result.output
95
+ assert "registered" in result.output
96
+ assert "probe ok" in result.output
@@ -39,6 +39,13 @@ class TestReplHelpers:
39
39
  from gdmcode.repl import _fmt_args
40
40
  assert _fmt_args({}) == ""
41
41
 
42
+ def test_connection_probe_detection(self) -> None:
43
+ from gdmcode.repl import _is_connection_probe
44
+
45
+ assert _is_connection_probe("hey")
46
+ assert _is_connection_probe("hello there")
47
+ assert not _is_connection_probe("fix the failing tests")
48
+
42
49
 
43
50
  class TestReplStartRepl:
44
51
  """Test start_repl runs and exits cleanly when given /exit input."""
@@ -157,6 +164,24 @@ class TestReplStartRepl:
157
164
  assert "/login grok" in output
158
165
  assert "/proxy token" in output
159
166
 
167
+ def test_plain_hey_with_credentials_uses_no_tool_probe(self, tmp_path, capsys) -> None:
168
+ from gdmcode.repl import start_repl
169
+
170
+ cfg, db = self._make_mocks(tmp_path)
171
+ cfg.api_key = "test-key"
172
+
173
+ with patch("gdmcode.repl._build_input_fn") as mock_build:
174
+ mock_build.return_value = MagicMock(side_effect=["hey", EOFError])
175
+ with patch("gdmcode.models.client.GdmClient.complete_text", return_value="Hey, LLM connected."):
176
+ with patch("gdmcode.repl._run_agent_turn") as run_agent:
177
+ with patch("gdmcode.cost_tracker.CostTracker"):
178
+ with patch("gdmcode.repl._ensure_session", return_value="session-123"):
179
+ start_repl(cfg, db)
180
+
181
+ output = capsys.readouterr().out
182
+ assert "LLM connected" in output
183
+ run_agent.assert_not_called()
184
+
160
185
 
161
186
  class TestReplErrorFormatting:
162
187
  def test_format_llm_error_rate_limit(self) -> None:
@@ -193,6 +218,17 @@ class TestReplErrorFormatting:
193
218
  output = capsys.readouterr().out
194
219
  assert "credit" in output.lower() or "quota" in output.lower()
195
220
 
221
+ def test_render_tool_call_stops_spinner_before_permission_work(self, capsys) -> None:
222
+ from gdmcode.agent.loop import EventType
223
+ from gdmcode.repl import _render_event
224
+
225
+ status = MagicMock()
226
+ console = MagicMock()
227
+ event = SimpleNamespace(type=EventType.TOOL_CALL, tool_name="bash", args={"command": "ls -F"})
228
+
229
+ assert _render_event(event, status, console) is False
230
+ status.stop.assert_called_once()
231
+
196
232
 
197
233
  class TestCommandDispatcher:
198
234
  """Smoke tests for CommandDispatcher (called by start_repl)."""
@@ -1 +0,0 @@
1
- __version__ = "0.1.4"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes