gdmcode 0.1.8__tar.gz → 0.1.10__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 (269) hide show
  1. {gdmcode-0.1.8 → gdmcode-0.1.10}/PKG-INFO +1 -1
  2. gdmcode-0.1.10/gdmcode/__init__.py +1 -0
  3. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/loop.py +52 -28
  4. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/cli.py +12 -3
  5. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/integrations/mcp_server.py +1 -1
  6. gdmcode-0.1.10/gdmcode/intent_router.py +223 -0
  7. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/repl.py +15 -58
  8. {gdmcode-0.1.8 → gdmcode-0.1.10}/pyproject.toml +1 -1
  9. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_cli_smoke.py +22 -0
  10. gdmcode-0.1.10/tests/test_intent_router.py +51 -0
  11. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_loop.py +4 -0
  12. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_repl_smoke.py +22 -1
  13. gdmcode-0.1.8/gdmcode/__init__.py +0 -1
  14. {gdmcode-0.1.8 → gdmcode-0.1.10}/.gitignore +0 -0
  15. {gdmcode-0.1.8 → gdmcode-0.1.10}/CONTRIBUTING.md +0 -0
  16. {gdmcode-0.1.8 → gdmcode-0.1.10}/README.md +0 -0
  17. {gdmcode-0.1.8 → gdmcode-0.1.10}/config.toml +0 -0
  18. {gdmcode-0.1.8 → gdmcode-0.1.10}/docs/agentic-runtime-audit.md +0 -0
  19. {gdmcode-0.1.8 → gdmcode-0.1.10}/docs/architecture.md +0 -0
  20. {gdmcode-0.1.8 → gdmcode-0.1.10}/docs/cli-reference.md +0 -0
  21. {gdmcode-0.1.8 → gdmcode-0.1.10}/docs/configuration.md +0 -0
  22. {gdmcode-0.1.8 → gdmcode-0.1.10}/docs/deployment.md +0 -0
  23. {gdmcode-0.1.8 → gdmcode-0.1.10}/docs/plugin-guide.md +0 -0
  24. {gdmcode-0.1.8 → gdmcode-0.1.10}/docs/quick-start.md +0 -0
  25. {gdmcode-0.1.8 → gdmcode-0.1.10}/docs/security-hardening.md +0 -0
  26. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/_internal/__init__.py +0 -0
  27. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/_internal/constants.py +0 -0
  28. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/_internal/domain_skills.py +0 -0
  29. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/__init__.py +0 -0
  30. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/commit_classifier.py +0 -0
  31. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/context_budget.py +0 -0
  32. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/daemon.py +0 -0
  33. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/dag_validator.py +0 -0
  34. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/debug_loop.py +0 -0
  35. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/impact_analyzer.py +0 -0
  36. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/impact_graph.py +0 -0
  37. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/orchestrator.py +0 -0
  38. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/regression_guard.py +0 -0
  39. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/review_gate.py +0 -0
  40. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/risk_scorer.py +0 -0
  41. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/self_healing.py +0 -0
  42. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/smart_test_selector.py +0 -0
  43. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/system_prompt.py +0 -0
  44. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/task_tracker.py +0 -0
  45. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/test_validator.py +0 -0
  46. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/tool_orchestrator.py +0 -0
  47. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/transcript.py +0 -0
  48. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/verification_loop.py +0 -0
  49. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/work_director.py +0 -0
  50. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/agent/worktree_manager.py +0 -0
  51. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/artifacts/__init__.py +0 -0
  52. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/artifacts/artifact_store.py +0 -0
  53. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/artifacts/verification_graph.py +0 -0
  54. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/auth.py +0 -0
  55. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/commands.py +0 -0
  56. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/config.py +0 -0
  57. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/cost_tracker.py +0 -0
  58. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/db/__init__.py +0 -0
  59. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/db/migrations.py +0 -0
  60. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/enterprise/__init__.py +0 -0
  61. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/enterprise/audit_log.py +0 -0
  62. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/enterprise/identity.py +0 -0
  63. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/enterprise/rbac.py +0 -0
  64. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/enterprise/team_config.py +0 -0
  65. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/enterprise/usage_analytics.py +0 -0
  66. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/exceptions.py +0 -0
  67. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/git_workflow.py +0 -0
  68. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/integrations/__init__.py +0 -0
  69. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/integrations/github_actions.py +0 -0
  70. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/integrations/sentry_integration.py +0 -0
  71. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/integrations/sentry_server.py +0 -0
  72. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/integrations/webhook_security.py +0 -0
  73. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/main.py +0 -0
  74. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/memory/__init__.py +0 -0
  75. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/memory/code_index.py +0 -0
  76. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/memory/compressor.py +0 -0
  77. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/memory/context_memory.py +0 -0
  78. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/memory/continuous_memory.py +0 -0
  79. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/memory/conventions.py +0 -0
  80. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/memory/db.py +0 -0
  81. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/memory/document_index.py +0 -0
  82. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/memory/file_cache.py +0 -0
  83. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/memory/project_scanner.py +0 -0
  84. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/memory/session_store.py +0 -0
  85. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/models/__init__.py +0 -0
  86. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/models/client.py +0 -0
  87. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/models/definitions.py +0 -0
  88. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/models/router.py +0 -0
  89. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/models/schemas.py +0 -0
  90. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/permissions.py +0 -0
  91. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/remote/__init__.py +0 -0
  92. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/remote/command_filter.py +0 -0
  93. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/remote/models.py +0 -0
  94. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/remote/permission_handler.py +0 -0
  95. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/remote/phone_ui.py +0 -0
  96. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/remote/protocol.py +0 -0
  97. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/remote/qr.py +0 -0
  98. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/remote/server.py +0 -0
  99. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/remote/token_manager.py +0 -0
  100. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/remote/tunnel.py +0 -0
  101. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/runtime/__init__.py +0 -0
  102. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/runtime/branch_farm.py +0 -0
  103. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/runtime/replay.py +0 -0
  104. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/sandbox/__init__.py +0 -0
  105. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/sandbox/hermetic.py +0 -0
  106. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/sandbox/policy.py +0 -0
  107. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/sdk/__init__.py +0 -0
  108. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/sdk/plugin_base.py +0 -0
  109. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/sdk/plugin_host.py +0 -0
  110. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/sdk/plugin_loader.py +0 -0
  111. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/security.py +0 -0
  112. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/server/__init__.py +0 -0
  113. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/server/bridge.py +0 -0
  114. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/server/bridge_cli.py +0 -0
  115. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/server/bridge_client.py +0 -0
  116. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/server/protocol_version.py +0 -0
  117. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/session/__init__.py +0 -0
  118. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/session/event_fanout.py +0 -0
  119. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/session/input_broker.py +0 -0
  120. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/session/permission_bridge.py +0 -0
  121. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/__init__.py +0 -0
  122. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/_atomic.py +0 -0
  123. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/agent_tools.py +0 -0
  124. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/ask_user_tool.py +0 -0
  125. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/bash_tool.py +0 -0
  126. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/browser_tool.py +0 -0
  127. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/browser_tools.py +0 -0
  128. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/dep_tools.py +0 -0
  129. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/document_reader.py +0 -0
  130. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/document_tool.py +0 -0
  131. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/document_writer.py +0 -0
  132. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/impact_tools.py +0 -0
  133. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/playwright_tool.py +0 -0
  134. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/quality_tools.py +0 -0
  135. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/read_tools.py +0 -0
  136. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/result_cache.py +0 -0
  137. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/search_tools.py +0 -0
  138. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/shell_tools.py +0 -0
  139. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/tools/write_tools.py +0 -0
  140. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/voice/__init__.py +0 -0
  141. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/voice/audio_capture.py +0 -0
  142. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/voice/audio_playback.py +0 -0
  143. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/voice/errors.py +0 -0
  144. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/voice/models.py +0 -0
  145. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/voice/providers.py +0 -0
  146. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/voice/vad.py +0 -0
  147. {gdmcode-0.1.8 → gdmcode-0.1.10}/gdmcode/voice/voice_loop.py +0 -0
  148. {gdmcode-0.1.8 → gdmcode-0.1.10}/proxy/Dockerfile +0 -0
  149. {gdmcode-0.1.8 → gdmcode-0.1.10}/proxy/main.py +0 -0
  150. {gdmcode-0.1.8 → gdmcode-0.1.10}/proxy/requirements.txt +0 -0
  151. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/__init__.py +0 -0
  152. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/remote/__init__.py +0 -0
  153. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/remote/test_remote_server.py +0 -0
  154. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_agent_loop.py +0 -0
  155. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_agent_tools.py +0 -0
  156. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_api_fallback.py +0 -0
  157. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_artifact_store.py +0 -0
  158. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_audit_log.py +0 -0
  159. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_auth.py +0 -0
  160. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_auto_quality.py +0 -0
  161. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_autonomy_levels.py +0 -0
  162. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_bash_tool.py +0 -0
  163. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_batch_api.py +0 -0
  164. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_branch_farm.py +0 -0
  165. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_bridge.py +0 -0
  166. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_bridge_smoke.py +0 -0
  167. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_browser_tool_smoke.py +0 -0
  168. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_browser_tools.py +0 -0
  169. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_btw_queue.py +0 -0
  170. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_budget_tracker.py +0 -0
  171. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_chrome_extension.py +0 -0
  172. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_ci_runner.py +0 -0
  173. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_code_index.py +0 -0
  174. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_commands.py +0 -0
  175. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_compression.py +0 -0
  176. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_confidence.py +0 -0
  177. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_config.py +0 -0
  178. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_continuous_memory.py +0 -0
  179. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_convention_drift.py +0 -0
  180. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_cost_tracker.py +0 -0
  181. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_daemon.py +0 -0
  182. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_daemon_stability.py +0 -0
  183. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_daemon_watchdog.py +0 -0
  184. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_db.py +0 -0
  185. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_debate.py +0 -0
  186. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_debug_loop.py +0 -0
  187. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_debug_loop_smoke.py +0 -0
  188. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_dep_tools.py +0 -0
  189. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_doctor.py +0 -0
  190. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_document_index.py +0 -0
  191. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_document_reader.py +0 -0
  192. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_document_tool.py +0 -0
  193. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_document_writer.py +0 -0
  194. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_domain_skills.py +0 -0
  195. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_eval_harness.py +0 -0
  196. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_event_log.py +0 -0
  197. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_failure_taxonomy.py +0 -0
  198. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_file_tools.py +0 -0
  199. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_git_workflow.py +0 -0
  200. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_github_actions.py +0 -0
  201. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_health.py +0 -0
  202. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_hermetic_sandbox.py +0 -0
  203. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_identity_rbac.py +0 -0
  204. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_impact_analysis.py +0 -0
  205. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_impact_analyzer.py +0 -0
  206. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_impact_graph.py +0 -0
  207. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_impact_tools.py +0 -0
  208. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_injection_gate.py +0 -0
  209. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_leaderboard.py +0 -0
  210. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_local_models.py +0 -0
  211. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_loop_p3.py +0 -0
  212. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_mcp_server.py +0 -0
  213. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_memory.py +0 -0
  214. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_migrations.py +0 -0
  215. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_mock_provider.py +0 -0
  216. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_model_config.py +0 -0
  217. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_orchestrator.py +0 -0
  218. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_package.py +0 -0
  219. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_permissions.py +0 -0
  220. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_phase2_modules.py +0 -0
  221. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_phone_ui.py +0 -0
  222. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_playwright_tool.py +0 -0
  223. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_plugin_sdk.py +0 -0
  224. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_protocol_version.py +0 -0
  225. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_provenance.py +0 -0
  226. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_proxy_server.py +0 -0
  227. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_quality_integration.py +0 -0
  228. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_reasoning_toggle.py +0 -0
  229. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_redaction.py +0 -0
  230. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_regression_collector.py +0 -0
  231. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_regression_guard_integration.py +0 -0
  232. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_regression_runner.py +0 -0
  233. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_replay.py +0 -0
  234. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_resilience.py +0 -0
  235. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_result_cache.py +0 -0
  236. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_review_gate_expanded.py +0 -0
  237. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_risk_scorer.py +0 -0
  238. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_rollback.py +0 -0
  239. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_router.py +0 -0
  240. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_router_compressor_conventions.py +0 -0
  241. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_router_escalation.py +0 -0
  242. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_sandbox.py +0 -0
  243. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_scoring.py +0 -0
  244. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_search_tools.py +0 -0
  245. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_self_healing.py +0 -0
  246. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_semantic_edit.py +0 -0
  247. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_sentry_integration.py +0 -0
  248. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_session_checkpoint.py +0 -0
  249. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_session_controller.py +0 -0
  250. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_session_restore.py +0 -0
  251. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_signal_handling.py +0 -0
  252. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_swebench_adapter.py +0 -0
  253. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_swebench_runner.py +0 -0
  254. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_system_prompt.py +0 -0
  255. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_team_config.py +0 -0
  256. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_tool_cache.py +0 -0
  257. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_tool_orchestrator.py +0 -0
  258. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_tool_timeout.py +0 -0
  259. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_tools_registry.py +0 -0
  260. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_tunnel_qr.py +0 -0
  261. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_usage_analytics.py +0 -0
  262. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_verification_graph.py +0 -0
  263. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_verification_loop.py +0 -0
  264. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_voice_loop.py +0 -0
  265. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_voice_providers.py +0 -0
  266. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_whole_codebase.py +0 -0
  267. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/test_work_director.py +0 -0
  268. {gdmcode-0.1.8 → gdmcode-0.1.10}/tests/voice/__init__.py +0 -0
  269. {gdmcode-0.1.8 → gdmcode-0.1.10}/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.8
3
+ Version: 0.1.10
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.10"
@@ -50,33 +50,33 @@ try:
50
50
  _analytics: "_UsageAnalytics | None" = _UsageAnalytics()
51
51
  except Exception: # noqa: BLE001
52
52
  _analytics = None
53
-
54
- try:
55
- from gdmcode.agent.risk_scorer import score_patch, RiskTier
56
- _risk_scorer_available = True
57
- except Exception: # noqa: BLE001
58
- _risk_scorer_available = False
59
-
60
-
61
- def _check_patch_risk(diff, file_paths=None, autonomy_level=2):
62
- """Score a patch diff and enforce risk gates based on autonomy level.
63
-
64
- - CRITICAL (score >= block_threshold) and autonomy_level < 4: raises RuntimeError.
65
- - HIGH tier and autonomy_level < 3: logs a warning (non-blocking).
66
- - Always returns the PatchRiskResult (or None if scorer unavailable).
67
- """
68
- if not _risk_scorer_available:
69
- return None
70
- result = score_patch(diff, file_paths)
71
- log.info("patch_risk score=%.3f tier=%s blocked=%s", result.score, result.tier, result.blocked)
72
- if result.triggered_signals:
73
- log.debug("patch_risk rationale:\n%s", result.rationale)
74
- if result.blocked and autonomy_level < 4:
75
- raise RuntimeError(f"Patch blocked: {result.rationale}")
76
- if result.tier == RiskTier.HIGH and autonomy_level < 3:
77
- log.warning("High-risk patch detected (score=%.3f). Review before applying:\n%s",
78
- result.score, result.rationale)
79
- return result
53
+
54
+ try:
55
+ from gdmcode.agent.risk_scorer import score_patch, RiskTier
56
+ _risk_scorer_available = True
57
+ except Exception: # noqa: BLE001
58
+ _risk_scorer_available = False
59
+
60
+
61
+ def _check_patch_risk(diff, file_paths=None, autonomy_level=2):
62
+ """Score a patch diff and enforce risk gates based on autonomy level.
63
+
64
+ - CRITICAL (score >= block_threshold) and autonomy_level < 4: raises RuntimeError.
65
+ - HIGH tier and autonomy_level < 3: logs a warning (non-blocking).
66
+ - Always returns the PatchRiskResult (or None if scorer unavailable).
67
+ """
68
+ if not _risk_scorer_available:
69
+ return None
70
+ result = score_patch(diff, file_paths)
71
+ log.info("patch_risk score=%.3f tier=%s blocked=%s", result.score, result.tier, result.blocked)
72
+ if result.triggered_signals:
73
+ log.debug("patch_risk rationale:\n%s", result.rationale)
74
+ if result.blocked and autonomy_level < 4:
75
+ raise RuntimeError(f"Patch blocked: {result.rationale}")
76
+ if result.tier == RiskTier.HIGH and autonomy_level < 3:
77
+ log.warning("High-risk patch detected (score=%.3f). Review before applying:\n%s",
78
+ result.score, result.rationale)
79
+ return result
80
80
 
81
81
  def _record_usage(
82
82
  session_id: str,
@@ -505,6 +505,11 @@ class AgentLoop:
505
505
  self._model = get_model(tier, self._cfg.provider)
506
506
  self._model_id = self._model.id
507
507
  self._gdm_client = self._make_client()
508
+ yield AgentEvent(
509
+ EventType.THINKING,
510
+ content=f"Using {self._model_id} for this turn.",
511
+ turn=0,
512
+ )
508
513
  log.debug(
509
514
  "ModelRouter selected tier=%s mode=%s for prompt=%r",
510
515
  tier,
@@ -587,6 +592,14 @@ class AgentLoop:
587
592
  log.warning("BTW queue drain failed: %s", _btw_exc)
588
593
 
589
594
  tools = self._build_tool_specs()
595
+ yield AgentEvent(
596
+ EventType.THINKING,
597
+ content=(
598
+ "Asking the model to choose the next step"
599
+ + (f" with {len(tools)} available tools." if tools else ".")
600
+ ),
601
+ turn=turn_num,
602
+ )
590
603
  try:
591
604
  response = self._gdm_client.complete(
592
605
  self._transcript.to_messages(),
@@ -686,6 +699,13 @@ class AgentLoop:
686
699
 
687
700
  # Execute tool calls
688
701
  if raw_tool_calls:
702
+ tool_names = ", ".join(str(tc.get("function", {}).get("name", "tool")) for tc in raw_tool_calls[:3])
703
+ extra = "" if len(raw_tool_calls) <= 3 else f" (+{len(raw_tool_calls) - 3} more)"
704
+ yield AgentEvent(
705
+ EventType.THINKING,
706
+ content=f"Model selected tool step: {tool_names}{extra}.",
707
+ turn=turn_num,
708
+ )
689
709
  # Guard: finish_reason=tool_calls with empty list → infinite loop
690
710
  if not msg.tool_calls:
691
711
  yield AgentEvent(EventType.ERROR, turn=turn_num,
@@ -694,6 +714,11 @@ class AgentLoop:
694
714
  return
695
715
 
696
716
  yield from self._execute_tool_calls(msg.tool_calls, turn_num)
717
+ yield AgentEvent(
718
+ EventType.THINKING,
719
+ content="Feeding tool results back to the model.",
720
+ turn=turn_num,
721
+ )
697
722
  continue # loop back to model with tool results
698
723
 
699
724
  # Natural completion
@@ -1407,4 +1432,3 @@ def write_autonomy_audit(
1407
1432
  (session_id, time.time(), level, action, json.dumps(details), checkpoint_id),
1408
1433
  )
1409
1434
  db_conn.commit()
1410
-
@@ -29,6 +29,7 @@ import shutil
29
29
  import httpx
30
30
  import typer
31
31
  from rich.console import Console
32
+ from rich.markup import escape
32
33
 
33
34
  from gdmcode import __version__
34
35
  from gdmcode.config import load_config
@@ -1231,14 +1232,15 @@ def _run_one_shot(cfg: object, prompt: str, *, yes: bool, model_override: str |
1231
1232
  """
1232
1233
  from rich.status import Status
1233
1234
  from gdmcode.agent.loop import EventType
1234
- from gdmcode.repl import _format_llm_error, _is_connection_probe
1235
+ from gdmcode.intent_router import PromptRoute, classify_prompt_route
1236
+ from gdmcode.repl import _format_llm_error
1235
1237
 
1236
1238
  if not _has_model_connection(cfg):
1237
1239
  console.print("[red]No model connection configured.[/red]")
1238
1240
  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].")
1239
1241
  raise typer.Exit(1)
1240
1242
 
1241
- if _is_connection_probe(prompt):
1243
+ if classify_prompt_route(prompt, cfg, model_override=model_override) == PromptRoute.LIGHTWEIGHT:
1242
1244
  _run_connection_probe_once(cfg, prompt, model_override=model_override)
1243
1245
  return
1244
1246
 
@@ -1300,7 +1302,14 @@ def _run_one_shot(cfg: object, prompt: str, *, yes: bool, model_override: str |
1300
1302
  had_error = False
1301
1303
  try:
1302
1304
  for event in loop.run(prompt): # type: ignore[union-attr]
1303
- if event.type == EventType.RESPONSE:
1305
+ if event.type == EventType.THINKING:
1306
+ status.stop()
1307
+ snippet = str(event.content or "")[:120]
1308
+ if snippet:
1309
+ console.print(f"[dim cyan]… {snippet}[/dim cyan]")
1310
+ status.start()
1311
+ status.update("[cyan]Working...[/cyan]")
1312
+ elif event.type == EventType.RESPONSE:
1304
1313
  status.stop()
1305
1314
  console.print(event.content)
1306
1315
  elif event.type == EventType.ERROR:
@@ -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.8") -> None:
54
+ def __init__(self, name: str = "gdmcode", version: str = "0.1.10") -> None:
55
55
  self._name = name
56
56
  self._version = version
57
57
  self._tools: dict[str, MCPTool] = {}
@@ -0,0 +1,223 @@
1
+ """Prompt intent routing for lightweight chat vs full agent mode."""
2
+ from __future__ import annotations
3
+
4
+ import logging
5
+ import re
6
+ from enum import Enum
7
+
8
+ log = logging.getLogger(__name__)
9
+
10
+ __all__ = [
11
+ "PromptRoute",
12
+ "classify_prompt_route",
13
+ "is_lightweight_prompt_heuristic",
14
+ ]
15
+
16
+
17
+ class PromptRoute(str, Enum):
18
+ """Possible execution routes for a user prompt."""
19
+
20
+ LIGHTWEIGHT = "LIGHTWEIGHT"
21
+ AGENT = "AGENT"
22
+
23
+
24
+ _WORK_TERMS: frozenset[str] = frozenset(
25
+ {
26
+ "fix",
27
+ "edit",
28
+ "change",
29
+ "create",
30
+ "write",
31
+ "delete",
32
+ "read",
33
+ "open",
34
+ "search",
35
+ "find",
36
+ "run",
37
+ "test",
38
+ "build",
39
+ "install",
40
+ "commit",
41
+ "review",
42
+ "debug",
43
+ "explain",
44
+ "refactor",
45
+ "implement",
46
+ "update",
47
+ "file",
48
+ "folder",
49
+ "repo",
50
+ "code",
51
+ "command",
52
+ "shell",
53
+ "terminal",
54
+ "powershell",
55
+ "bash",
56
+ "internet",
57
+ "web",
58
+ "download",
59
+ }
60
+ )
61
+
62
+ _LIGHTWEIGHT_EXACT: frozenset[str] = frozenset(
63
+ {
64
+ "hey",
65
+ "hi",
66
+ "hello",
67
+ "yo",
68
+ "ping",
69
+ "hello there",
70
+ "hey there",
71
+ "hi there",
72
+ "are you there",
73
+ "you there",
74
+ "who are you",
75
+ "what are you",
76
+ "what can you do",
77
+ "are you working",
78
+ "are you connected",
79
+ "is the llm connected",
80
+ "status",
81
+ }
82
+ )
83
+
84
+ _LIGHTWEIGHT_PREFIXES: tuple[str, ...] = (
85
+ "who are you",
86
+ "what are you",
87
+ "what can you do",
88
+ "are you there",
89
+ "are you connected",
90
+ )
91
+
92
+ _CLASSIFIER_SYSTEM = """Classify the user's prompt for a CLI coding agent.
93
+
94
+ Reply with exactly one token:
95
+ LIGHTWEIGHT - greetings, identity/capability questions, connection/status checks, or normal chat that does not need files, repo context, tools, internet, or commands.
96
+ AGENT - anything that asks to inspect, read, search, edit, create, delete, run, test, build, install, debug, explain code/files/repo, use tools, access internet, or continue previous work.
97
+
98
+ When uncertain, reply AGENT.
99
+ """
100
+
101
+
102
+ def _normalize(text: str) -> str:
103
+ normalized = re.sub(r"[^a-z0-9\s']", " ", text.lower()).strip()
104
+ return re.sub(r"\s+", " ", normalized)
105
+
106
+
107
+ def _looks_like_agent_work(text: str, normalized: str, words: list[str]) -> bool:
108
+ if any(word in _WORK_TERMS for word in words):
109
+ return True
110
+ if len(words) > 16:
111
+ return True
112
+ if "```" in text or "`" in text:
113
+ return True
114
+ if re.search(r"(?:^|\s)(?:\.?[/\\]|[a-zA-Z]:\\)", text):
115
+ return True
116
+ if re.search(r"\b[\w.-]+\.(?:py|js|ts|tsx|jsx|json|toml|yaml|yml|md|txt|go|rs|java|cs|cpp|c|h|html|css)\b", text, re.I):
117
+ return True
118
+ if normalized.startswith(("can you ", "please ", "could you ")) and any(word in _WORK_TERMS for word in normalized.split()):
119
+ return True
120
+ return False
121
+
122
+
123
+ def is_lightweight_prompt_heuristic(text: str) -> bool:
124
+ """Return True only for prompts that are clearly lightweight without LLM help."""
125
+ normalized = _normalize(text)
126
+ if not normalized:
127
+ return False
128
+ words = normalized.split()
129
+ if _looks_like_agent_work(text, normalized, words):
130
+ return False
131
+ if normalized in _LIGHTWEIGHT_EXACT:
132
+ return True
133
+ if len(words) <= 8 and normalized.startswith(_LIGHTWEIGHT_PREFIXES):
134
+ return True
135
+ return len(words) <= 4 and words[0] in {"hey", "hi", "hello"} and all(
136
+ word in {"hey", "hi", "hello", "there", "gdm", "agent", "please"} for word in words
137
+ )
138
+
139
+
140
+ def _has_model_route(cfg: object) -> bool:
141
+ return bool(
142
+ getattr(cfg, "api_key", "")
143
+ or (
144
+ getattr(cfg, "proxy_enabled", False)
145
+ and getattr(cfg, "proxy_url", "")
146
+ and getattr(cfg, "proxy_token", "")
147
+ )
148
+ )
149
+
150
+
151
+ def _classifier_client(cfg: object):
152
+ from gdmcode.models.client import GdmClient
153
+
154
+ proxy_active = bool(
155
+ getattr(cfg, "proxy_enabled", False)
156
+ and getattr(cfg, "proxy_url", "")
157
+ and getattr(cfg, "proxy_token", "")
158
+ )
159
+ return (
160
+ GdmClient.for_proxy(getattr(cfg, "proxy_url"), getattr(cfg, "proxy_token"))
161
+ if proxy_active
162
+ else GdmClient(cfg) # type: ignore[arg-type]
163
+ )
164
+
165
+
166
+ def _parse_route(raw: str) -> PromptRoute:
167
+ return PromptRoute.LIGHTWEIGHT if raw.strip().upper() == PromptRoute.LIGHTWEIGHT else PromptRoute.AGENT
168
+
169
+
170
+ def classify_prompt_route(
171
+ prompt: str,
172
+ cfg: object,
173
+ *,
174
+ model_override: str | None = None,
175
+ allow_llm: bool = True,
176
+ ) -> PromptRoute:
177
+ """Classify a prompt into lightweight chat or full agent mode.
178
+
179
+ The router is intentionally conservative: obvious work never calls the
180
+ classifier, unparseable classifier output becomes AGENT, and classifier
181
+ failures fall back only to the strict allow-list heuristic.
182
+ """
183
+ normalized = _normalize(prompt)
184
+ if not normalized:
185
+ return PromptRoute.AGENT
186
+
187
+ words = normalized.split()
188
+ if _looks_like_agent_work(prompt, normalized, words):
189
+ log.debug("prompt_route route=AGENT source=heuristic_work prompt=%r", prompt[:80])
190
+ return PromptRoute.AGENT
191
+
192
+ if is_lightweight_prompt_heuristic(prompt):
193
+ log.debug("prompt_route route=LIGHTWEIGHT source=heuristic_light prompt=%r", prompt[:80])
194
+ return PromptRoute.LIGHTWEIGHT
195
+
196
+ if not allow_llm or not _has_model_route(cfg):
197
+ route = PromptRoute.LIGHTWEIGHT if is_lightweight_prompt_heuristic(prompt) else PromptRoute.AGENT
198
+ log.debug("prompt_route route=%s source=fallback_no_llm prompt=%r", route.value, prompt[:80])
199
+ return route
200
+
201
+ try:
202
+ from gdmcode.models.definitions import ModelTier, get_model
203
+
204
+ tier = model_override or ModelTier.SCOUT
205
+ model_def = get_model(tier, getattr(cfg, "provider"))
206
+ raw = _classifier_client(cfg).complete_text(
207
+ prompt,
208
+ model=model_def.id,
209
+ max_tokens=4,
210
+ system=_CLASSIFIER_SYSTEM,
211
+ )
212
+ route = _parse_route(raw)
213
+ log.debug(
214
+ "prompt_route route=%s source=llm raw=%r prompt=%r",
215
+ route.value,
216
+ raw[:80],
217
+ prompt[:80],
218
+ )
219
+ return route
220
+ except Exception as exc: # noqa: BLE001
221
+ fallback = PromptRoute.LIGHTWEIGHT if is_lightweight_prompt_heuristic(prompt) else PromptRoute.AGENT
222
+ log.warning("Prompt intent classifier failed; using %s fallback: %s", fallback.value, exc)
223
+ return fallback
@@ -163,59 +163,10 @@ def _format_llm_error(error: object) -> str:
163
163
 
164
164
 
165
165
  def _is_connection_probe(text: str) -> bool:
166
- """Return True for lightweight chat/status prompts 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
- work_terms = {
174
- "fix", "edit", "change", "create", "write", "delete", "read", "open", "search",
175
- "find", "run", "test", "build", "install", "commit", "review", "debug", "explain",
176
- "refactor", "implement", "update", "file", "folder", "repo", "code",
177
- }
178
- words = normalized.split()
179
- if any(word in work_terms for word in words):
180
- return False
181
- direct = {
182
- "hey",
183
- "hi",
184
- "hello",
185
- "yo",
186
- "ping",
187
- "test",
188
- "hello there",
189
- "hey there",
190
- "hi there",
191
- "are you there",
192
- "you there",
193
- "who are you",
194
- "what are you",
195
- "what can you do",
196
- "are you working",
197
- "are you connected",
198
- "is the llm connected",
199
- "are tools working",
200
- "do tools work",
201
- "status",
202
- }
203
- if normalized in direct:
204
- return True
205
- prefixes = (
206
- "who are you",
207
- "what are you",
208
- "what can you do",
209
- "are you there",
210
- "are you connected",
211
- "are tools working",
212
- "do tools work",
213
- )
214
- if len(words) <= 8 and normalized.startswith(prefixes):
215
- return True
216
- return len(words) <= 4 and words[0] in {"hey", "hi", "hello"} and all(
217
- word in {"hey", "hi", "hello", "there", "gdm", "agent", "please"} for word in words
218
- )
166
+ """Compatibility wrapper for tests and older imports."""
167
+ from gdmcode.intent_router import is_lightweight_prompt_heuristic
168
+
169
+ return is_lightweight_prompt_heuristic(text)
219
170
 
220
171
 
221
172
  def _render_event(event: object, status: Status, console: Console) -> bool:
@@ -226,8 +177,11 @@ def _render_event(event: object, status: Status, console: Console) -> bool:
226
177
  match ev.type: # type: ignore[union-attr]
227
178
  case EventType.THINKING:
228
179
  snippet = str(ev.content or "")[:100] # type: ignore[union-attr]
229
- label = f"[dim cyan]Thinking... {snippet}[/dim cyan]"
230
- status.update(label)
180
+ status.stop()
181
+ if snippet:
182
+ console.print(f"[dim cyan]… {escape(snippet)}[/dim cyan]")
183
+ status.start()
184
+ status.update("[cyan]Working...[/cyan]")
231
185
  case EventType.TOOL_CALL:
232
186
  status.stop()
233
187
  console.print(f"[yellow][tool] {ev.tool_name}({_fmt_args(ev.args)})[/yellow]") # type: ignore[union-attr]
@@ -690,9 +644,12 @@ def start_repl(cfg: "GdmConfig", db: "GdmDatabase", *, yes: bool = False, model_
690
644
  console.print("[yellow]Restore not supported by current agent.[/yellow]")
691
645
  continue
692
646
 
693
- if _is_connection_probe(text):
694
- _run_connection_probe(text)
695
- continue
647
+ if loop is None:
648
+ from gdmcode.intent_router import PromptRoute, classify_prompt_route
649
+
650
+ if classify_prompt_route(text, cfg, model_override=model_override) == PromptRoute.LIGHTWEIGHT:
651
+ _run_connection_probe(text)
652
+ continue
696
653
 
697
654
  pending = _show_pending_btw(db, session_id, console)
698
655
  full_message = text
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "gdmcode"
7
- version = "0.1.8"
7
+ version = "0.1.10"
8
8
  description = "gdm: AI coding agent for professional developers"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -132,3 +132,25 @@ class TestCliSmoke:
132
132
  assert result.exit_code == 0
133
133
  probe.assert_called_once()
134
134
  assert probe.call_args.args[1] == "who are you?"
135
+
136
+ def test_one_shot_ambiguous_prompt_uses_llm_router(self, tmp_path, monkeypatch) -> None:
137
+ import gdmcode.cli as cli
138
+ import gdmcode.intent_router as intent_router
139
+
140
+ cfg = SimpleNamespace(provider="gemini", api_key="key", project_root=tmp_path)
141
+ monkeypatch.setattr(cli, "load_config", lambda require_credentials=False: cfg)
142
+ monkeypatch.setattr(cli, "_print_header", lambda _cfg: None)
143
+ monkeypatch.setattr(cli, "_has_model_connection", lambda _cfg: True)
144
+ monkeypatch.setattr(
145
+ intent_router,
146
+ "classify_prompt_route",
147
+ lambda *_args, **_kwargs: intent_router.PromptRoute.LIGHTWEIGHT,
148
+ )
149
+ probe = MagicMock()
150
+ monkeypatch.setattr(cli, "_run_connection_probe_once", probe)
151
+
152
+ result = runner.invoke(app, ["code", "--prompt", "what's your name?"])
153
+
154
+ assert result.exit_code == 0
155
+ probe.assert_called_once()
156
+ assert probe.call_args.args[1] == "what's your name?"
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from types import SimpleNamespace
4
+ from unittest.mock import patch
5
+
6
+
7
+ def _cfg() -> SimpleNamespace:
8
+ return SimpleNamespace(
9
+ provider="gemini",
10
+ api_key="key",
11
+ proxy_enabled=False,
12
+ proxy_url="",
13
+ proxy_token="",
14
+ )
15
+
16
+
17
+ def test_obvious_work_routes_to_agent_without_llm() -> None:
18
+ from gdmcode.intent_router import PromptRoute, classify_prompt_route
19
+
20
+ with patch("gdmcode.models.client.GdmClient.complete_text") as complete_text:
21
+ route = classify_prompt_route("can you inspect the repo and run tests?", _cfg())
22
+
23
+ assert route == PromptRoute.AGENT
24
+ complete_text.assert_not_called()
25
+
26
+
27
+ def test_ambiguous_prompt_can_be_classified_lightweight_by_llm() -> None:
28
+ from gdmcode.intent_router import PromptRoute, classify_prompt_route
29
+
30
+ with patch("gdmcode.models.client.GdmClient.complete_text", return_value="LIGHTWEIGHT"):
31
+ route = classify_prompt_route("what's your name?", _cfg())
32
+
33
+ assert route == PromptRoute.LIGHTWEIGHT
34
+
35
+
36
+ def test_unparseable_classifier_output_defaults_to_agent() -> None:
37
+ from gdmcode.intent_router import PromptRoute, classify_prompt_route
38
+
39
+ with patch("gdmcode.models.client.GdmClient.complete_text", return_value="This seems lightweight."):
40
+ route = classify_prompt_route("what's your name?", _cfg())
41
+
42
+ assert route == PromptRoute.AGENT
43
+
44
+
45
+ def test_classifier_error_falls_back_conservatively() -> None:
46
+ from gdmcode.intent_router import PromptRoute, classify_prompt_route
47
+
48
+ with patch("gdmcode.models.client.GdmClient.complete_text", side_effect=RuntimeError("boom")):
49
+ route = classify_prompt_route("what's your name?", _cfg())
50
+
51
+ assert route == PromptRoute.AGENT
@@ -268,6 +268,10 @@ class TestAgentLoop:
268
268
 
269
269
  mock_orchestrator.execute.assert_called_once_with("bash", {"command": "ls"}, model_id=loop._model.id)
270
270
  event_types = [e.type for e in events]
271
+ thinking = [e.content for e in events if e.type == EventType.THINKING]
272
+ assert any("Asking the model" in content for content in thinking)
273
+ assert any("Model selected tool step: bash" in content for content in thinking)
274
+ assert any("Feeding tool results back" in content for content in thinking)
271
275
  assert EventType.TOOL_CALL in event_types
272
276
  assert EventType.TOOL_RESULT in event_types
273
277
  assert EventType.DONE in event_types
@@ -46,7 +46,7 @@ class TestReplHelpers:
46
46
  assert _is_connection_probe("hello there")
47
47
  assert _is_connection_probe("who are you?")
48
48
  assert _is_connection_probe("what can you do?")
49
- assert _is_connection_probe("are tools working?")
49
+ assert not _is_connection_probe("are tools working?")
50
50
  assert not _is_connection_probe("fix the failing tests")
51
51
  assert not _is_connection_probe("explain this file")
52
52
 
@@ -204,6 +204,27 @@ class TestReplStartRepl:
204
204
  assert "coding-agent CLI" in output
205
205
  run_agent.assert_not_called()
206
206
 
207
+ def test_ambiguous_lightweight_prompt_uses_llm_router(self, tmp_path, capsys) -> None:
208
+ from gdmcode.repl import start_repl
209
+
210
+ cfg, db = self._make_mocks(tmp_path)
211
+ cfg.api_key = "test-key"
212
+
213
+ with patch("gdmcode.repl._build_input_fn") as mock_build:
214
+ mock_build.return_value = MagicMock(side_effect=["what's your name?", EOFError])
215
+ with patch(
216
+ "gdmcode.models.client.GdmClient.complete_text",
217
+ side_effect=["LIGHTWEIGHT", "I am gdm, your coding-agent CLI."],
218
+ ):
219
+ with patch("gdmcode.repl._run_agent_turn") as run_agent:
220
+ with patch("gdmcode.cost_tracker.CostTracker"):
221
+ with patch("gdmcode.repl._ensure_session", return_value="session-123"):
222
+ start_repl(cfg, db)
223
+
224
+ output = capsys.readouterr().out
225
+ assert "coding-agent CLI" in output
226
+ run_agent.assert_not_called()
227
+
207
228
 
208
229
  class TestReplErrorFormatting:
209
230
  def test_format_llm_error_rate_limit(self) -> None:
@@ -1 +0,0 @@
1
- __version__ = "0.1.8"
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