gdmcode 0.1.4__tar.gz → 0.1.6__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 (268) hide show
  1. {gdmcode-0.1.4 → gdmcode-0.1.6}/PKG-INFO +1 -1
  2. gdmcode-0.1.6/gdmcode/__init__.py +1 -0
  3. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/tool_orchestrator.py +3 -11
  4. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/cli.py +118 -6
  5. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/integrations/mcp_server.py +1 -1
  6. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/main.py +14 -0
  7. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/models/definitions.py +2 -3
  8. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/repl.py +79 -0
  9. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/__init__.py +15 -0
  10. {gdmcode-0.1.4 → gdmcode-0.1.6}/pyproject.toml +1 -1
  11. gdmcode-0.1.6/tests/test_cli_smoke.py +115 -0
  12. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_health.py +9 -0
  13. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_repl_smoke.py +36 -0
  14. gdmcode-0.1.4/gdmcode/__init__.py +0 -1
  15. gdmcode-0.1.4/tests/test_cli_smoke.py +0 -59
  16. {gdmcode-0.1.4 → gdmcode-0.1.6}/.gitignore +0 -0
  17. {gdmcode-0.1.4 → gdmcode-0.1.6}/CONTRIBUTING.md +0 -0
  18. {gdmcode-0.1.4 → gdmcode-0.1.6}/README.md +0 -0
  19. {gdmcode-0.1.4 → gdmcode-0.1.6}/config.toml +0 -0
  20. {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/agentic-runtime-audit.md +0 -0
  21. {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/architecture.md +0 -0
  22. {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/cli-reference.md +0 -0
  23. {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/configuration.md +0 -0
  24. {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/deployment.md +0 -0
  25. {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/plugin-guide.md +0 -0
  26. {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/quick-start.md +0 -0
  27. {gdmcode-0.1.4 → gdmcode-0.1.6}/docs/security-hardening.md +0 -0
  28. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/_internal/__init__.py +0 -0
  29. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/_internal/constants.py +0 -0
  30. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/_internal/domain_skills.py +0 -0
  31. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/__init__.py +0 -0
  32. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/commit_classifier.py +0 -0
  33. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/context_budget.py +0 -0
  34. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/daemon.py +0 -0
  35. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/dag_validator.py +0 -0
  36. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/debug_loop.py +0 -0
  37. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/impact_analyzer.py +0 -0
  38. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/impact_graph.py +0 -0
  39. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/loop.py +0 -0
  40. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/orchestrator.py +0 -0
  41. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/regression_guard.py +0 -0
  42. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/review_gate.py +0 -0
  43. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/risk_scorer.py +0 -0
  44. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/self_healing.py +0 -0
  45. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/smart_test_selector.py +0 -0
  46. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/system_prompt.py +0 -0
  47. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/task_tracker.py +0 -0
  48. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/test_validator.py +0 -0
  49. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/transcript.py +0 -0
  50. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/verification_loop.py +0 -0
  51. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/work_director.py +0 -0
  52. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/agent/worktree_manager.py +0 -0
  53. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/artifacts/__init__.py +0 -0
  54. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/artifacts/artifact_store.py +0 -0
  55. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/artifacts/verification_graph.py +0 -0
  56. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/auth.py +0 -0
  57. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/commands.py +0 -0
  58. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/config.py +0 -0
  59. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/cost_tracker.py +0 -0
  60. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/db/__init__.py +0 -0
  61. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/db/migrations.py +0 -0
  62. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/enterprise/__init__.py +0 -0
  63. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/enterprise/audit_log.py +0 -0
  64. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/enterprise/identity.py +0 -0
  65. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/enterprise/rbac.py +0 -0
  66. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/enterprise/team_config.py +0 -0
  67. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/enterprise/usage_analytics.py +0 -0
  68. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/exceptions.py +0 -0
  69. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/git_workflow.py +0 -0
  70. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/integrations/__init__.py +0 -0
  71. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/integrations/github_actions.py +0 -0
  72. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/integrations/sentry_integration.py +0 -0
  73. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/integrations/sentry_server.py +0 -0
  74. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/integrations/webhook_security.py +0 -0
  75. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/__init__.py +0 -0
  76. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/code_index.py +0 -0
  77. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/compressor.py +0 -0
  78. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/context_memory.py +0 -0
  79. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/continuous_memory.py +0 -0
  80. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/conventions.py +0 -0
  81. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/db.py +0 -0
  82. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/document_index.py +0 -0
  83. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/file_cache.py +0 -0
  84. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/project_scanner.py +0 -0
  85. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/memory/session_store.py +0 -0
  86. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/models/__init__.py +0 -0
  87. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/models/client.py +0 -0
  88. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/models/router.py +0 -0
  89. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/models/schemas.py +0 -0
  90. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/permissions.py +0 -0
  91. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/__init__.py +0 -0
  92. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/command_filter.py +0 -0
  93. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/models.py +0 -0
  94. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/permission_handler.py +0 -0
  95. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/phone_ui.py +0 -0
  96. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/protocol.py +0 -0
  97. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/qr.py +0 -0
  98. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/server.py +0 -0
  99. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/token_manager.py +0 -0
  100. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/remote/tunnel.py +0 -0
  101. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/runtime/__init__.py +0 -0
  102. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/runtime/branch_farm.py +0 -0
  103. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/runtime/replay.py +0 -0
  104. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/sandbox/__init__.py +0 -0
  105. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/sandbox/hermetic.py +0 -0
  106. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/sandbox/policy.py +0 -0
  107. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/sdk/__init__.py +0 -0
  108. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/sdk/plugin_base.py +0 -0
  109. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/sdk/plugin_host.py +0 -0
  110. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/sdk/plugin_loader.py +0 -0
  111. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/security.py +0 -0
  112. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/server/__init__.py +0 -0
  113. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/server/bridge.py +0 -0
  114. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/server/bridge_cli.py +0 -0
  115. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/server/bridge_client.py +0 -0
  116. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/server/protocol_version.py +0 -0
  117. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/session/__init__.py +0 -0
  118. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/session/event_fanout.py +0 -0
  119. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/session/input_broker.py +0 -0
  120. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/session/permission_bridge.py +0 -0
  121. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/_atomic.py +0 -0
  122. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/agent_tools.py +0 -0
  123. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/ask_user_tool.py +0 -0
  124. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/bash_tool.py +0 -0
  125. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/browser_tool.py +0 -0
  126. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/browser_tools.py +0 -0
  127. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/dep_tools.py +0 -0
  128. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/document_reader.py +0 -0
  129. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/document_tool.py +0 -0
  130. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/document_writer.py +0 -0
  131. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/impact_tools.py +0 -0
  132. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/playwright_tool.py +0 -0
  133. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/quality_tools.py +0 -0
  134. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/read_tools.py +0 -0
  135. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/result_cache.py +0 -0
  136. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/search_tools.py +0 -0
  137. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/shell_tools.py +0 -0
  138. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/tools/write_tools.py +0 -0
  139. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/__init__.py +0 -0
  140. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/audio_capture.py +0 -0
  141. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/audio_playback.py +0 -0
  142. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/errors.py +0 -0
  143. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/models.py +0 -0
  144. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/providers.py +0 -0
  145. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/vad.py +0 -0
  146. {gdmcode-0.1.4 → gdmcode-0.1.6}/gdmcode/voice/voice_loop.py +0 -0
  147. {gdmcode-0.1.4 → gdmcode-0.1.6}/proxy/Dockerfile +0 -0
  148. {gdmcode-0.1.4 → gdmcode-0.1.6}/proxy/main.py +0 -0
  149. {gdmcode-0.1.4 → gdmcode-0.1.6}/proxy/requirements.txt +0 -0
  150. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/__init__.py +0 -0
  151. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/remote/__init__.py +0 -0
  152. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/remote/test_remote_server.py +0 -0
  153. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_agent_loop.py +0 -0
  154. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_agent_tools.py +0 -0
  155. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_api_fallback.py +0 -0
  156. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_artifact_store.py +0 -0
  157. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_audit_log.py +0 -0
  158. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_auth.py +0 -0
  159. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_auto_quality.py +0 -0
  160. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_autonomy_levels.py +0 -0
  161. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_bash_tool.py +0 -0
  162. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_batch_api.py +0 -0
  163. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_branch_farm.py +0 -0
  164. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_bridge.py +0 -0
  165. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_bridge_smoke.py +0 -0
  166. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_browser_tool_smoke.py +0 -0
  167. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_browser_tools.py +0 -0
  168. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_btw_queue.py +0 -0
  169. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_budget_tracker.py +0 -0
  170. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_chrome_extension.py +0 -0
  171. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_ci_runner.py +0 -0
  172. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_code_index.py +0 -0
  173. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_commands.py +0 -0
  174. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_compression.py +0 -0
  175. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_confidence.py +0 -0
  176. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_config.py +0 -0
  177. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_continuous_memory.py +0 -0
  178. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_convention_drift.py +0 -0
  179. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_cost_tracker.py +0 -0
  180. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_daemon.py +0 -0
  181. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_daemon_stability.py +0 -0
  182. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_daemon_watchdog.py +0 -0
  183. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_db.py +0 -0
  184. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_debate.py +0 -0
  185. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_debug_loop.py +0 -0
  186. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_debug_loop_smoke.py +0 -0
  187. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_dep_tools.py +0 -0
  188. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_doctor.py +0 -0
  189. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_document_index.py +0 -0
  190. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_document_reader.py +0 -0
  191. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_document_tool.py +0 -0
  192. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_document_writer.py +0 -0
  193. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_domain_skills.py +0 -0
  194. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_eval_harness.py +0 -0
  195. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_event_log.py +0 -0
  196. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_failure_taxonomy.py +0 -0
  197. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_file_tools.py +0 -0
  198. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_git_workflow.py +0 -0
  199. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_github_actions.py +0 -0
  200. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_hermetic_sandbox.py +0 -0
  201. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_identity_rbac.py +0 -0
  202. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_impact_analysis.py +0 -0
  203. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_impact_analyzer.py +0 -0
  204. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_impact_graph.py +0 -0
  205. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_impact_tools.py +0 -0
  206. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_injection_gate.py +0 -0
  207. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_leaderboard.py +0 -0
  208. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_local_models.py +0 -0
  209. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_loop.py +0 -0
  210. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_loop_p3.py +0 -0
  211. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_mcp_server.py +0 -0
  212. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_memory.py +0 -0
  213. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_migrations.py +0 -0
  214. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_mock_provider.py +0 -0
  215. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_model_config.py +0 -0
  216. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_orchestrator.py +0 -0
  217. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_package.py +0 -0
  218. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_permissions.py +0 -0
  219. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_phase2_modules.py +0 -0
  220. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_phone_ui.py +0 -0
  221. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_playwright_tool.py +0 -0
  222. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_plugin_sdk.py +0 -0
  223. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_protocol_version.py +0 -0
  224. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_provenance.py +0 -0
  225. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_proxy_server.py +0 -0
  226. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_quality_integration.py +0 -0
  227. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_reasoning_toggle.py +0 -0
  228. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_redaction.py +0 -0
  229. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_regression_collector.py +0 -0
  230. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_regression_guard_integration.py +0 -0
  231. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_regression_runner.py +0 -0
  232. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_replay.py +0 -0
  233. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_resilience.py +0 -0
  234. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_result_cache.py +0 -0
  235. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_review_gate_expanded.py +0 -0
  236. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_risk_scorer.py +0 -0
  237. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_rollback.py +0 -0
  238. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_router.py +0 -0
  239. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_router_compressor_conventions.py +0 -0
  240. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_router_escalation.py +0 -0
  241. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_sandbox.py +0 -0
  242. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_scoring.py +0 -0
  243. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_search_tools.py +0 -0
  244. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_self_healing.py +0 -0
  245. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_semantic_edit.py +0 -0
  246. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_sentry_integration.py +0 -0
  247. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_session_checkpoint.py +0 -0
  248. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_session_controller.py +0 -0
  249. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_session_restore.py +0 -0
  250. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_signal_handling.py +0 -0
  251. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_swebench_adapter.py +0 -0
  252. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_swebench_runner.py +0 -0
  253. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_system_prompt.py +0 -0
  254. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_team_config.py +0 -0
  255. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_tool_cache.py +0 -0
  256. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_tool_orchestrator.py +0 -0
  257. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_tool_timeout.py +0 -0
  258. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_tools_registry.py +0 -0
  259. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_tunnel_qr.py +0 -0
  260. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_usage_analytics.py +0 -0
  261. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_verification_graph.py +0 -0
  262. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_verification_loop.py +0 -0
  263. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_voice_loop.py +0 -0
  264. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_voice_providers.py +0 -0
  265. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_whole_codebase.py +0 -0
  266. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/test_work_director.py +0 -0
  267. {gdmcode-0.1.4 → gdmcode-0.1.6}/tests/voice/__init__.py +0 -0
  268. {gdmcode-0.1.4 → gdmcode-0.1.6}/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.6
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.6"
@@ -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
@@ -347,13 +348,61 @@ def cmd_health(
347
348
  except Exception as exc: # noqa: BLE001
348
349
  return CheckResult(f"api ({cfg.provider})", "fail", detail=str(exc)[:60])
349
350
 
351
+ def _check_completion() -> CheckResult:
352
+ t0 = time.monotonic()
353
+ try:
354
+ from gdmcode.models.client import GdmClient
355
+
356
+ model_def = get_model(ModelTier.CODER, cfg.provider)
357
+ proxy_active = bool(
358
+ getattr(cfg, "proxy_enabled", False)
359
+ and getattr(cfg, "proxy_url", "")
360
+ and getattr(cfg, "proxy_token", "")
361
+ )
362
+ client = (
363
+ GdmClient.for_proxy(getattr(cfg, "proxy_url"), getattr(cfg, "proxy_token"))
364
+ if proxy_active
365
+ else GdmClient(cfg)
366
+ )
367
+ text = client.complete_text(
368
+ "Reply with exactly: OK",
369
+ model=model_def.id,
370
+ max_tokens=8,
371
+ system="You are a health check. Reply only with OK.",
372
+ )
373
+ status = "ok" if text.strip() else "fail"
374
+ detail = f"{model_def.id}: {text.strip()[:30] or 'empty response'}"
375
+ return CheckResult("llm completion", status, latency_ms=int((time.monotonic() - t0) * 1000), detail=detail, priority="P0")
376
+ except Exception as exc: # noqa: BLE001
377
+ return CheckResult("llm completion", "fail", latency_ms=int((time.monotonic() - t0) * 1000), detail=str(exc)[:120], priority="P0")
378
+
350
379
  def _check_tools() -> CheckResult:
351
380
  t0 = time.monotonic()
352
381
  try:
353
382
  tools = REGISTRY.all_tools()
354
383
  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")
384
+ if count == 0:
385
+ return CheckResult("tools", "fail", latency_ms=int((time.monotonic() - t0) * 1000), detail="0 tools registered")
386
+ probe_name = "powershell" if sys.platform == "win32" else "bash"
387
+ probe_args = (
388
+ {"command": "$PWD.Path", "timeout": 5}
389
+ if probe_name == "powershell"
390
+ else {"command": "pwd", "timeout": 5, "read_only": True}
391
+ )
392
+ probe = REGISTRY.call(probe_name, probe_args)
393
+ if not probe.ok:
394
+ return CheckResult(
395
+ "tools",
396
+ "fail",
397
+ latency_ms=int((time.monotonic() - t0) * 1000),
398
+ detail=f"{count} registered; {probe_name} probe failed: {probe.error}",
399
+ )
400
+ return CheckResult(
401
+ "tools",
402
+ "ok",
403
+ latency_ms=int((time.monotonic() - t0) * 1000),
404
+ detail=f"{count} registered; {probe_name} probe ok",
405
+ )
357
406
  except Exception as exc: # noqa: BLE001
358
407
  return CheckResult("tools", "fail", detail=str(exc))
359
408
 
@@ -380,10 +429,11 @@ def cmd_health(
380
429
  return results
381
430
 
382
431
  # Run scalar checks in parallel; sysdeps is fast so fine in its own slot
383
- with ThreadPoolExecutor(max_workers=6) as executor:
432
+ with ThreadPoolExecutor(max_workers=7) as executor:
384
433
  f_db = executor.submit(_check_db)
385
434
  f_daemon = executor.submit(_check_daemon)
386
435
  f_api = executor.submit(_check_api)
436
+ f_completion = executor.submit(_check_completion)
387
437
  f_tools = executor.submit(_check_tools)
388
438
  f_budget = executor.submit(_check_budget)
389
439
  f_sys = executor.submit(_check_sysdeps)
@@ -392,13 +442,15 @@ def cmd_health(
392
442
  f_db.result(),
393
443
  f_daemon.result(),
394
444
  f_api.result(),
445
+ f_completion.result(),
395
446
  f_tools.result(),
396
447
  f_budget.result(),
397
448
  *f_sys.result(),
398
449
  ]
399
450
 
400
451
  overall_fail = any(
401
- r.status in ("fail", "missing") and r.priority in ("P0", "P1")
452
+ r.status == "fail" and r.priority in ("P0", "P1")
453
+ or r.status == "missing" and r.priority == "P0"
402
454
  for r in all_checks
403
455
  )
404
456
 
@@ -1179,12 +1231,17 @@ def _run_one_shot(cfg: object, prompt: str, *, yes: bool, model_override: str |
1179
1231
  """
1180
1232
  from rich.status import Status
1181
1233
  from gdmcode.agent.loop import EventType
1234
+ from gdmcode.repl import _format_llm_error, _is_connection_probe
1182
1235
 
1183
1236
  if not _has_model_connection(cfg):
1184
1237
  console.print("[red]No model connection configured.[/red]")
1185
1238
  console.print("Run [bold]gdm login grok[/bold], [bold]gdm login gemini[/bold], or start [bold]gdm[/bold] and use [bold]/proxy token[/bold] then [bold]/proxy on[/bold].")
1186
1239
  raise typer.Exit(1)
1187
1240
 
1241
+ if _is_connection_probe(prompt):
1242
+ _run_connection_probe_once(cfg, prompt, model_override=model_override)
1243
+ return
1244
+
1188
1245
  try:
1189
1246
  loop, cost_tracker, _db = _setup_agent(cfg, yes=yes, model_override=model_override)
1190
1247
  except Exception as exc: # noqa: BLE001
@@ -1248,10 +1305,26 @@ def _run_one_shot(cfg: object, prompt: str, *, yes: bool, model_override: str |
1248
1305
  console.print(event.content)
1249
1306
  elif event.type == EventType.ERROR:
1250
1307
  status.stop()
1251
- console.print(f"[red]Error:[/red] {event.content}")
1308
+ content = str(event.content or "")
1309
+ if content.lower().startswith("max turns"):
1310
+ console.print(f"[yellow]Agent stopped before completion:[/yellow] {content}")
1311
+ console.print("[dim]Increase --max-turns or simplify the prompt.[/dim]")
1312
+ else:
1313
+ console.print(_format_llm_error(event.content))
1252
1314
  had_error = True
1253
1315
  elif event.type == EventType.TOOL_CALL:
1316
+ status.stop()
1254
1317
  console.print(f"[yellow] [tool] {event.tool_name}[/yellow]")
1318
+ elif event.type == EventType.TOOL_RESULT:
1319
+ status.stop()
1320
+ result = event.result
1321
+ if result is not None and getattr(result, "error", None):
1322
+ console.print(f"[red] [tool error] {event.tool_name}:[/red] {getattr(result, 'error')}")
1323
+ had_error = True
1324
+ elif result is not None:
1325
+ output = (getattr(result, "output", "") or "").strip()
1326
+ if output:
1327
+ console.print(f"[dim]{output[:2000]}[/dim]")
1255
1328
  elif event.type == EventType.DONE:
1256
1329
  status.stop()
1257
1330
  except Exception as exc: # noqa: BLE001
@@ -1267,6 +1340,45 @@ def _run_one_shot(cfg: object, prompt: str, *, yes: bool, model_override: str |
1267
1340
  raise typer.Exit(1)
1268
1341
 
1269
1342
 
1343
+ def _run_connection_probe_once(cfg: object, prompt: str, *, model_override: str | None) -> None:
1344
+ """Run a one-shot no-tools LLM connection probe."""
1345
+ from rich.status import Status
1346
+ from gdmcode.models.client import GdmClient
1347
+ from gdmcode.models.definitions import ModelTier, get_model
1348
+ from gdmcode.repl import _format_llm_error
1349
+
1350
+ status = Status("[cyan]Checking LLM connection...[/cyan]", console=console, spinner="dots")
1351
+ status.start()
1352
+ try:
1353
+ tier = model_override or ModelTier.CODER
1354
+ model_def = get_model(tier, cfg.provider) # type: ignore[union-attr]
1355
+ proxy_active = bool(
1356
+ getattr(cfg, "proxy_enabled", False)
1357
+ and getattr(cfg, "proxy_url", "")
1358
+ and getattr(cfg, "proxy_token", "")
1359
+ )
1360
+ client = (
1361
+ GdmClient.for_proxy(getattr(cfg, "proxy_url"), getattr(cfg, "proxy_token"))
1362
+ if proxy_active
1363
+ else GdmClient(cfg) # type: ignore[arg-type]
1364
+ )
1365
+ response = client.complete_text(
1366
+ prompt,
1367
+ model=model_def.id,
1368
+ max_tokens=80,
1369
+ system=(
1370
+ "You are gdm code's CLI connection check. Reply in one short, friendly "
1371
+ "sentence. Confirm the LLM is connected. Do not inspect files or call tools."
1372
+ ),
1373
+ ).strip()
1374
+ status.stop()
1375
+ console.print(response or "[green]LLM connected.[/green]")
1376
+ except Exception as exc: # noqa: BLE001
1377
+ status.stop()
1378
+ console.print(_format_llm_error(exc))
1379
+ raise typer.Exit(1) from exc
1380
+
1381
+
1270
1382
  # ---------------------------------------------------------------------------
1271
1383
  # Helpers
1272
1384
  # ---------------------------------------------------------------------------
@@ -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.6") -> 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()
@@ -201,14 +201,14 @@ GEMINI_SCOUT = ModelDef(
201
201
  )
202
202
 
203
203
  GEMINI_CODER = ModelDef(
204
- id="gemini-2.5-flash-8b",
204
+ id="gemini-2.5-flash",
205
205
  tier=ModelTier.CODER,
206
206
  provider=Provider.GEMINI,
207
207
  input_per_m=0.075,
208
208
  output_per_m=0.30,
209
209
  cached_per_m=0.0,
210
210
  context_window=1_000_000,
211
- notes="Ultra-cheap bulk: file indexing, convention extraction, boilerplate edits.",
211
+ notes="Cheap bulk: file indexing, convention extraction, boilerplate edits.",
212
212
  )
213
213
 
214
214
  GEMINI_THINKER = ModelDef(
@@ -456,4 +456,3 @@ def get_capability(tier: str, provider: str, capability: str) -> bool:
456
456
  return bool(getattr(m, capability, False))
457
457
  except KeyError:
458
458
  return False
459
-
@@ -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.6"
8
8
  description = "gdm: AI coding agent for professional developers"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -0,0 +1,115 @@
1
+ """Smoke tests for gdm CLI entrypoints."""
2
+ from __future__ import annotations
3
+
4
+ from types import SimpleNamespace
5
+ from unittest.mock import MagicMock, patch
6
+
7
+ import pytest
8
+ from typer.testing import CliRunner
9
+
10
+ from gdmcode.cli import app
11
+
12
+ runner = CliRunner()
13
+
14
+
15
+ class TestCliSmoke:
16
+ def test_help_exits_zero(self) -> None:
17
+ result = runner.invoke(app, ["--help"])
18
+ assert result.exit_code == 0
19
+
20
+ def test_version_exits_zero(self) -> None:
21
+ result = runner.invoke(app, ["version"])
22
+ assert result.exit_code == 0
23
+
24
+ def test_doctor_exits_cleanly(self) -> None:
25
+ """doctor must exit 0 even if optional deps are missing."""
26
+ result = runner.invoke(app, ["doctor"])
27
+ assert result.exit_code == 0
28
+ assert "Core" in result.output
29
+
30
+ def test_doctor_shows_python_version(self) -> None:
31
+ result = runner.invoke(app, ["doctor"])
32
+ assert "Python" in result.output
33
+
34
+ def test_code_help_exits_zero(self) -> None:
35
+ result = runner.invoke(app, ["code", "--help"])
36
+ assert result.exit_code == 0
37
+
38
+ def test_login_help_exits_zero(self) -> None:
39
+ result = runner.invoke(app, ["login", "--help"])
40
+ assert result.exit_code == 0
41
+
42
+ def test_logout_help_exits_zero(self) -> None:
43
+ result = runner.invoke(app, ["logout", "--help"])
44
+ assert result.exit_code == 0
45
+
46
+ def test_daemon_help_exits_zero(self) -> None:
47
+ result = runner.invoke(app, ["daemon", "--help"])
48
+ assert result.exit_code == 0
49
+
50
+ def test_doctor_no_traceback_on_missing_deps(self) -> None:
51
+ """Graceful output even when optional packages absent."""
52
+ result = runner.invoke(app, ["doctor"])
53
+ assert "Traceback" not in result.output
54
+ assert "Traceback" not in (
55
+ result.exception.__class__.__name__ if result.exception else ""
56
+ )
57
+
58
+ def test_version_shows_version_string(self) -> None:
59
+ result = runner.invoke(app, ["version"])
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
+ with patch("gdmcode.models.client.GdmClient.complete_text", return_value="OK"):
93
+ result = runner.invoke(app, ["health"])
94
+
95
+ assert "tools" in result.output
96
+ assert "registered" in result.output
97
+ assert "probe ok" in result.output
98
+ assert "llm completion" in result.output
99
+
100
+ def test_one_shot_greeting_uses_connection_probe(self, tmp_path, monkeypatch) -> None:
101
+ import gdmcode.cli as cli
102
+
103
+ cfg = SimpleNamespace(provider="gemini", api_key="key", project_root=tmp_path)
104
+ monkeypatch.setattr(cli, "load_config", lambda require_credentials=False: cfg)
105
+ monkeypatch.setattr(cli, "_print_header", lambda _cfg: None)
106
+ monkeypatch.setattr(cli, "_has_model_connection", lambda _cfg: True)
107
+ probe = MagicMock()
108
+ monkeypatch.setattr(cli, "_run_connection_probe_once", probe)
109
+
110
+ result = runner.invoke(app, ["code", "--prompt", "hey"])
111
+
112
+ assert result.exit_code == 0
113
+ probe.assert_called_once()
114
+ assert probe.call_args.args[1] == "hey"
115
+ assert probe.call_args.kwargs == {"model_override": None}
@@ -22,6 +22,9 @@ def _make_cfg(**kwargs):
22
22
  cfg.provider = kwargs.get("provider", "grok")
23
23
  cfg.api_key = kwargs.get("api_key", "sk-test")
24
24
  cfg.project_root = Path("/tmp/gdm-test")
25
+ cfg.proxy_enabled = kwargs.get("proxy_enabled", False)
26
+ cfg.proxy_url = kwargs.get("proxy_url", "")
27
+ cfg.proxy_token = kwargs.get("proxy_token", None)
25
28
  return cfg
26
29
 
27
30
 
@@ -63,6 +66,7 @@ def _run_health(
63
66
  daemon = _make_daemon(running=daemon_running)
64
67
  mock_registry = MagicMock()
65
68
  mock_registry.all_tools.return_value = [MagicMock()] * tools_count
69
+ mock_registry.call.return_value = MagicMock(ok=True, output="ok", error=None)
66
70
 
67
71
  mock_response = MagicMock()
68
72
  mock_response.status_code = api_status
@@ -75,6 +79,7 @@ def _run_health(
75
79
  patch("gdmcode.cli.shutil.which", side_effect=lambda cmd: which_map.get(cmd)),
76
80
  patch("gdmcode.cli.httpx.get",
77
81
  side_effect=api_exc if api_exc else lambda *a, **kw: mock_response),
82
+ patch("gdmcode.models.client.GdmClient.complete_text", return_value="OK"),
78
83
  ):
79
84
  runner = CliRunner()
80
85
  result = runner.invoke(app, ["health", *args])
@@ -138,6 +143,7 @@ def test_api_probe_sends_auth_header():
138
143
  daemon = _make_daemon()
139
144
  mock_registry = MagicMock()
140
145
  mock_registry.all_tools.return_value = [MagicMock()]
146
+ mock_registry.call.return_value = MagicMock(ok=True, output="ok", error=None)
141
147
 
142
148
  with (
143
149
  patch("gdmcode.cli.load_config", return_value=cfg),
@@ -146,6 +152,7 @@ def test_api_probe_sends_auth_header():
146
152
  patch("gdmcode.cli.REGISTRY", mock_registry),
147
153
  patch("gdmcode.cli.shutil.which", return_value="/usr/bin/git"),
148
154
  patch("gdmcode.cli.httpx.get", side_effect=fake_get),
155
+ patch("gdmcode.models.client.GdmClient.complete_text", return_value="OK"),
149
156
  ):
150
157
  runner = CliRunner()
151
158
  runner.invoke(app, ["health"])
@@ -280,6 +287,7 @@ def test_parallel_execution_completes():
280
287
  daemon = _make_daemon()
281
288
  mock_registry = MagicMock()
282
289
  mock_registry.all_tools.return_value = [MagicMock()]
290
+ mock_registry.call.return_value = MagicMock(ok=True, output="ok", error=None)
283
291
 
284
292
  def slow_api(*a, **kw):
285
293
  import time as _t
@@ -295,6 +303,7 @@ def test_parallel_execution_completes():
295
303
  patch("gdmcode.cli.REGISTRY", mock_registry),
296
304
  patch("gdmcode.cli.shutil.which", return_value="/bin/git"),
297
305
  patch("gdmcode.cli.httpx.get", side_effect=slow_api),
306
+ patch("gdmcode.models.client.GdmClient.complete_text", return_value="OK"),
298
307
  ):
299
308
  runner = CliRunner()
300
309
  t0 = time.monotonic()
@@ -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)."""