specsmith 0.10.1.dev250__tar.gz → 0.10.1.dev262__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 (234) hide show
  1. {specsmith-0.10.1.dev250/src/specsmith.egg-info → specsmith-0.10.1.dev262}/PKG-INFO +7 -1
  2. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/pyproject.toml +12 -1
  3. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/chat_runner.py +208 -50
  4. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/repl.py +11 -33
  5. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/runner.py +8 -0
  6. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/cli.py +243 -0
  7. specsmith-0.10.1.dev262/src/specsmith/commands/__init__.py +7 -0
  8. specsmith-0.10.1.dev262/src/specsmith/commands/ask.py +21 -0
  9. specsmith-0.10.1.dev262/src/specsmith/commands/commit.py +31 -0
  10. specsmith-0.10.1.dev262/src/specsmith/commands/context.py +17 -0
  11. specsmith-0.10.1.dev262/src/specsmith/commands/fix.py +19 -0
  12. specsmith-0.10.1.dev262/src/specsmith/commands/plan.py +19 -0
  13. specsmith-0.10.1.dev262/src/specsmith/commands/registry.py +94 -0
  14. specsmith-0.10.1.dev262/src/specsmith/commands/review.py +18 -0
  15. specsmith-0.10.1.dev262/src/specsmith/commands/test.py +17 -0
  16. specsmith-0.10.1.dev262/src/specsmith/eval/__init__.py +17 -0
  17. specsmith-0.10.1.dev262/src/specsmith/eval/graders.py +137 -0
  18. specsmith-0.10.1.dev262/src/specsmith/eval/harness.py +78 -0
  19. specsmith-0.10.1.dev262/src/specsmith/eval/types.py +94 -0
  20. specsmith-0.10.1.dev262/src/specsmith/feature_flags.py +90 -0
  21. specsmith-0.10.1.dev262/src/specsmith/instinct.py +172 -0
  22. specsmith-0.10.1.dev262/src/specsmith/tui/__init__.py +24 -0
  23. specsmith-0.10.1.dev262/src/specsmith/tui/app.py +133 -0
  24. specsmith-0.10.1.dev262/src/specsmith/tui/bridge.py +227 -0
  25. {specsmith-0.10.1.dev250/src/specsmith/commands → specsmith-0.10.1.dev262/src/specsmith/tui/screens}/__init__.py +1 -1
  26. specsmith-0.10.1.dev262/src/specsmith/tui/screens/project.py +93 -0
  27. specsmith-0.10.1.dev262/src/specsmith/tui/screens/session.py +170 -0
  28. specsmith-0.10.1.dev262/src/specsmith/tui/screens/settings.py +100 -0
  29. specsmith-0.10.1.dev262/src/specsmith/tui/themes/__init__.py +3 -0
  30. specsmith-0.10.1.dev262/src/specsmith/tui/widgets/__init__.py +3 -0
  31. specsmith-0.10.1.dev262/src/specsmith/tui/widgets/chat.py +132 -0
  32. specsmith-0.10.1.dev262/src/specsmith/tui/widgets/command_block.py +74 -0
  33. specsmith-0.10.1.dev262/src/specsmith/tui/widgets/governance_bar.py +66 -0
  34. specsmith-0.10.1.dev262/src/specsmith/tui/widgets/input_bar.py +117 -0
  35. specsmith-0.10.1.dev262/src/specsmith/tui/widgets/provider_bar.py +123 -0
  36. specsmith-0.10.1.dev262/src/specsmith/tui/widgets/token_meter.py +37 -0
  37. specsmith-0.10.1.dev262/src/specsmith/tui/widgets/tool_block.py +99 -0
  38. specsmith-0.10.1.dev262/src/specsmith/tui/widgets/vcs_bar.py +60 -0
  39. specsmith-0.10.1.dev262/src/specsmith/webui/__init__.py +29 -0
  40. specsmith-0.10.1.dev262/src/specsmith/webui/app.py +636 -0
  41. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262/src/specsmith.egg-info}/PKG-INFO +7 -1
  42. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith.egg-info/SOURCES.txt +38 -0
  43. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith.egg-info/requires.txt +8 -0
  44. specsmith-0.10.1.dev262/tests/test_commands.py +139 -0
  45. specsmith-0.10.1.dev262/tests/test_eval.py +156 -0
  46. specsmith-0.10.1.dev262/tests/test_feature_flags.py +76 -0
  47. specsmith-0.10.1.dev262/tests/test_instinct.py +82 -0
  48. specsmith-0.10.1.dev262/tests/test_tui.py +342 -0
  49. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/LICENSE +0 -0
  50. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/README.md +0 -0
  51. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/setup.cfg +0 -0
  52. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/epistemic/__init__.py +0 -0
  53. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/epistemic/belief.py +0 -0
  54. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/epistemic/certainty.py +0 -0
  55. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/epistemic/failure_graph.py +0 -0
  56. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/epistemic/py.typed +0 -0
  57. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/epistemic/recovery.py +0 -0
  58. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/epistemic/session.py +0 -0
  59. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/epistemic/stress_tester.py +0 -0
  60. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/epistemic/trace.py +0 -0
  61. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/__init__.py +0 -0
  62. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/__main__.py +0 -0
  63. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/__init__.py +0 -0
  64. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/broker.py +0 -0
  65. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/cleanup.py +0 -0
  66. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/core.py +0 -0
  67. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/endpoints.py +0 -0
  68. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/events.py +0 -0
  69. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/fallback.py +0 -0
  70. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/indexer.py +0 -0
  71. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/mcp.py +0 -0
  72. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/memory.py +0 -0
  73. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/orchestrator.py +0 -0
  74. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/profiles.py +0 -0
  75. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/router.py +0 -0
  76. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/rules.py +0 -0
  77. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/safety.py +0 -0
  78. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/suggester.py +0 -0
  79. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/tools.py +0 -0
  80. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/verifier.py +0 -0
  81. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/agent/voice.py +0 -0
  82. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/architect.py +0 -0
  83. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/auditor.py +0 -0
  84. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/auth.py +0 -0
  85. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/block_export.py +0 -0
  86. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/compressor.py +0 -0
  87. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/config.py +0 -0
  88. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/console_utils.py +0 -0
  89. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/credit_analyzer.py +0 -0
  90. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/credits.py +0 -0
  91. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/differ.py +0 -0
  92. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/doctor.py +0 -0
  93. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/drive.py +0 -0
  94. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/epistemic/__init__.py +0 -0
  95. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/epistemic/belief.py +0 -0
  96. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/epistemic/certainty.py +0 -0
  97. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/epistemic/failure_graph.py +0 -0
  98. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/epistemic/recovery.py +0 -0
  99. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/epistemic/stress_tester.py +0 -0
  100. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/executor.py +0 -0
  101. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/exporter.py +0 -0
  102. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/gui/__init__.py +0 -0
  103. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/gui/app.py +0 -0
  104. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/gui/main_window.py +0 -0
  105. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/gui/session_tab.py +0 -0
  106. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/gui/theme.py +0 -0
  107. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/gui/widgets/__init__.py +0 -0
  108. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/gui/widgets/chat_view.py +0 -0
  109. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/gui/widgets/input_bar.py +0 -0
  110. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/gui/widgets/provider_bar.py +0 -0
  111. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/gui/widgets/token_meter.py +0 -0
  112. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/gui/widgets/tool_panel.py +0 -0
  113. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/gui/widgets/update_checker.py +0 -0
  114. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/gui/worker.py +0 -0
  115. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/history_search.py +0 -0
  116. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/importer.py +0 -0
  117. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/integrations/__init__.py +0 -0
  118. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/integrations/agent_skill.py +0 -0
  119. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/integrations/aider.py +0 -0
  120. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/integrations/base.py +0 -0
  121. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/integrations/claude_code.py +0 -0
  122. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/integrations/copilot.py +0 -0
  123. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/integrations/cursor.py +0 -0
  124. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/integrations/gemini.py +0 -0
  125. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/integrations/windsurf.py +0 -0
  126. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/languages.py +0 -0
  127. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/ledger.py +0 -0
  128. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/patent.py +0 -0
  129. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/phase.py +0 -0
  130. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/plugins.py +0 -0
  131. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/profiles.py +0 -0
  132. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/rate_limits.py +0 -0
  133. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/releaser.py +0 -0
  134. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/requirements.py +0 -0
  135. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/requirements_parser.py +0 -0
  136. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/retrieval.py +0 -0
  137. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/scaffolder.py +0 -0
  138. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/serve.py +0 -0
  139. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/session.py +0 -0
  140. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/skills.py +0 -0
  141. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/agents.md.j2 +0 -0
  142. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/community/bug_report.md.j2 +0 -0
  143. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/community/code_of_conduct.md.j2 +0 -0
  144. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/community/contributing.md.j2 +0 -0
  145. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/community/feature_request.md.j2 +0 -0
  146. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/community/license-Apache-2.0.j2 +0 -0
  147. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/community/license-MIT.j2 +0 -0
  148. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/community/pull_request_template.md.j2 +0 -0
  149. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/community/security.md.j2 +0 -0
  150. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
  151. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/docs/mkdocs.yml.j2 +0 -0
  152. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/docs/readthedocs.yaml.j2 +0 -0
  153. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
  154. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
  155. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/editorconfig.j2 +0 -0
  156. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/gitattributes.j2 +0 -0
  157. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/gitignore.j2 +0 -0
  158. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/go/go.mod.j2 +0 -0
  159. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/go/main.go.j2 +0 -0
  160. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/governance/belief-registry.md.j2 +0 -0
  161. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
  162. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
  163. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/governance/epistemic-axioms.md.j2 +0 -0
  164. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/governance/failure-modes.md.j2 +0 -0
  165. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/governance/lifecycle.md.j2 +0 -0
  166. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/governance/roles.md.j2 +0 -0
  167. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/governance/rules.md.j2 +0 -0
  168. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/governance/session-protocol.md.j2 +0 -0
  169. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/governance/uncertainty-map.md.j2 +0 -0
  170. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/governance/verification.md.j2 +0 -0
  171. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/js/package.json.j2 +0 -0
  172. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/ledger.md.j2 +0 -0
  173. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/python/cli.py.j2 +0 -0
  174. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/python/init.py.j2 +0 -0
  175. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/python/pyproject.toml.j2 +0 -0
  176. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/readme.md.j2 +0 -0
  177. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/rust/Cargo.toml.j2 +0 -0
  178. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/rust/main.rs.j2 +0 -0
  179. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
  180. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
  181. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
  182. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
  183. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
  184. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
  185. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/templates/workflows/release.yml.j2 +0 -0
  186. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/tool_installer.py +0 -0
  187. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/toolrules.py +0 -0
  188. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/tools.py +0 -0
  189. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/trace.py +0 -0
  190. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/updater.py +0 -0
  191. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/upgrader.py +0 -0
  192. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/validator.py +0 -0
  193. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/vcs/__init__.py +0 -0
  194. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/vcs/base.py +0 -0
  195. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/vcs/bitbucket.py +0 -0
  196. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/vcs/github.py +0 -0
  197. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/vcs/gitlab.py +0 -0
  198. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/vcs_commands.py +0 -0
  199. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/wireframes.py +0 -0
  200. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith/workspace.py +0 -0
  201. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith.egg-info/dependency_links.txt +0 -0
  202. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith.egg-info/entry_points.txt +0 -0
  203. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/src/specsmith.egg-info/top_level.txt +0 -0
  204. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_CMD_001.py +0 -0
  205. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_agent_profiles.py +0 -0
  206. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_agent_runner_ready.py +0 -0
  207. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_auditor.py +0 -0
  208. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_chat_diff_decision.py +0 -0
  209. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_chat_runner_openai_compat.py +0 -0
  210. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_chat_stdin_protocol.py +0 -0
  211. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_cli.py +0 -0
  212. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_cli_workflows_history_drive.py +0 -0
  213. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_compressor.py +0 -0
  214. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_e2e_nexus.py +0 -0
  215. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_endpoints_cli.py +0 -0
  216. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_endpoints_store.py +0 -0
  217. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_epistemic.py +0 -0
  218. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_fallback_chain.py +0 -0
  219. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_importer.py +0 -0
  220. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_integrations.py +0 -0
  221. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_mcp_client.py +0 -0
  222. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_nexus.py +0 -0
  223. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_phase1_4_new.py +0 -0
  224. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_phase34_completion.py +0 -0
  225. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_rate_limits.py +0 -0
  226. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_scaffolder.py +0 -0
  227. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_skill_marketplace.py +0 -0
  228. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_smoke.py +0 -0
  229. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_suggester.py +0 -0
  230. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_tools.py +0 -0
  231. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_validator.py +0 -0
  232. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_vcs.py +0 -0
  233. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_warp_parity.py +0 -0
  234. {specsmith-0.10.1.dev250 → specsmith-0.10.1.dev262}/tests/test_warp_parity_followup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specsmith
3
- Version: 0.10.1.dev250
3
+ Version: 0.10.1.dev262
4
4
  Summary: Applied Epistemic Engineering toolkit — AEE agent sessions, execution profiles, FPGA/HDL governance, tool installer, 50+ CLI commands.
5
5
  Author: BitConcepts
6
6
  License-Expression: MIT
@@ -38,6 +38,8 @@ Requires-Dist: ruff>=0.4; extra == "dev"
38
38
  Requires-Dist: mypy>=1.10; extra == "dev"
39
39
  Requires-Dist: pre-commit>=3.0; extra == "dev"
40
40
  Requires-Dist: types-pyyaml>=6.0; extra == "dev"
41
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
42
+ Requires-Dist: pytest-textual-snapshot>=1.0; extra == "dev"
41
43
  Provides-Extra: docs
42
44
  Requires-Dist: mkdocs>=1.6; extra == "docs"
43
45
  Requires-Dist: mkdocs-material>=9.5; extra == "docs"
@@ -51,6 +53,10 @@ Provides-Extra: mistral
51
53
  Requires-Dist: openai>=1.0; extra == "mistral"
52
54
  Provides-Extra: gui
53
55
  Requires-Dist: PySide6>=6.6; extra == "gui"
56
+ Provides-Extra: tui
57
+ Requires-Dist: textual>=3.0; extra == "tui"
58
+ Provides-Extra: webui
59
+ Requires-Dist: nicegui>=3.0; extra == "webui"
54
60
  Provides-Extra: ag2
55
61
  Requires-Dist: ag2[ollama]; extra == "ag2"
56
62
  Provides-Extra: history-semantic
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "specsmith"
7
- version = "0.10.1.dev250"
7
+ version = "0.10.1.dev262"
8
8
  description = "Applied Epistemic Engineering toolkit — AEE agent sessions, execution profiles, FPGA/HDL governance, tool installer, 50+ CLI commands."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -51,6 +51,8 @@ dev = [
51
51
  "mypy>=1.10",
52
52
  "pre-commit>=3.0",
53
53
  "types-pyyaml>=6.0",
54
+ "pytest-asyncio>=0.23",
55
+ "pytest-textual-snapshot>=1.0",
54
56
  ]
55
57
  docs = [
56
58
  "mkdocs>=1.6",
@@ -65,6 +67,8 @@ openai = ["openai>=1.0"]
65
67
  gemini = ["google-genai>=1.0"]
66
68
  mistral = ["openai>=1.0"] # Mistral uses the openai SDK pointed at api.mistral.ai
67
69
  gui = ["PySide6>=6.6"]
70
+ tui = ["textual>=3.0"]
71
+ webui = ["nicegui>=3.0"]
68
72
  # AG2 agent shell (Planner/Builder/Verifier over Ollama)
69
73
  ag2 = ["ag2[ollama]"]
70
74
  # Optional semantic backend for `specsmith history search --semantic` (REQ-135).
@@ -182,8 +186,15 @@ module = [
182
186
  "specsmith.serve",
183
187
  "specsmith.toolrules",
184
188
  "specsmith.tool_installer",
189
+ "specsmith.commands.*",
190
+ "specsmith.eval.*",
191
+ "specsmith.feature_flags",
192
+ "specsmith.instinct",
193
+ "specsmith.tui.*",
194
+ "specsmith.webui.*",
185
195
  ]
186
196
  ignore_errors = true
187
197
 
188
198
  [tool.pytest.ini_options]
189
199
  testpaths = ["tests"]
200
+ asyncio_mode = "auto"
@@ -17,6 +17,7 @@ green on machines that have no LLM configured at all.
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
+ import inspect as _inspect
20
21
  import json
21
22
  import os
22
23
  from dataclasses import dataclass, field
@@ -324,7 +325,7 @@ def _run_openai(
324
325
  # ``stream_options.include_usage`` makes the final SSE chunk carry a
325
326
  # populated ``usage`` block (otherwise streaming responses emit it as
326
327
  # ``None``). Older SDK versions silently ignore unknown kwargs.
327
- stream = client.chat.completions.create(
328
+ stream = client.chat.completions.create( # type: ignore[call-overload,unused-ignore]
328
329
  model=os.environ.get("OPENAI_MODEL", "gpt-4o-mini"),
329
330
  messages=messages,
330
331
  stream=True,
@@ -344,6 +345,63 @@ def _run_openai(
344
345
  return ("".join(pieces) if pieces else None), usage
345
346
 
346
347
 
348
+ # ---------------------------------------------------------------------------
349
+ # OpenAI function-calling tool definitions (generated from AVAILABLE_TOOLS)
350
+ # ---------------------------------------------------------------------------
351
+
352
+ _TOOL_DEFS: list[dict[str, Any]] | None = None
353
+ _TOOL_MAP: dict[str, Any] | None = None
354
+
355
+
356
+ def _openai_tool_defs() -> tuple[list[dict[str, Any]], dict[str, Any]]:
357
+ """Build OpenAI function-calling tool definitions from AVAILABLE_TOOLS.
358
+
359
+ Returns ``(definitions, name_to_func_map)``.
360
+ """
361
+ global _TOOL_DEFS, _TOOL_MAP # noqa: PLW0603
362
+ if _TOOL_DEFS is not None and _TOOL_MAP is not None:
363
+ return _TOOL_DEFS, _TOOL_MAP
364
+
365
+ from specsmith.agent.tools import AVAILABLE_TOOLS
366
+
367
+ TYPE_MAP = {str: "string", int: "integer", float: "number", bool: "boolean"}
368
+ defs: list[dict[str, Any]] = []
369
+ fmap: dict[str, Any] = {}
370
+ for fn in AVAILABLE_TOOLS:
371
+ sig = _inspect.signature(fn)
372
+ props: dict[str, Any] = {}
373
+ required: list[str] = []
374
+ for pname, param in sig.parameters.items():
375
+ ann = param.annotation
376
+ # Unwrap Optional (str | None)
377
+ origin = getattr(ann, "__origin__", None)
378
+ if origin is type(str | None): # types.UnionType
379
+ args = [a for a in ann.__args__ if a is not type(None)]
380
+ ann = args[0] if args else str
381
+ ptype = TYPE_MAP.get(ann, "string")
382
+ props[pname] = {"type": ptype}
383
+ if param.default is _inspect.Parameter.empty:
384
+ required.append(pname)
385
+ defs.append(
386
+ {
387
+ "type": "function",
388
+ "function": {
389
+ "name": fn.__name__,
390
+ "description": (fn.__doc__ or "").strip().split("\n")[0],
391
+ "parameters": {
392
+ "type": "object",
393
+ "properties": props,
394
+ "required": required,
395
+ },
396
+ },
397
+ }
398
+ )
399
+ fmap[fn.__name__] = fn
400
+ _TOOL_DEFS = defs
401
+ _TOOL_MAP = fmap
402
+ return defs, fmap
403
+
404
+
347
405
  def _run_openai_compat(
348
406
  messages: list[dict[str, str]],
349
407
  emitter: EventEmitter,
@@ -353,18 +411,16 @@ def _run_openai_compat(
353
411
  ) -> tuple[str | None, _UsageDelta]:
354
412
  """Stream from a user-registered OpenAI-v1-compatible endpoint (REQ-142).
355
413
 
356
- Uses raw stdlib HTTP so the openai SDK is not a hard dependency for
357
- BYOE. Sends a streaming ``/chat/completions`` request, decodes the
358
- Server-Sent-Events ``data:`` lines, and forwards each ``content``
359
- delta as a ``token`` event on ``block_id``.
414
+ Supports tool calling: sends tool definitions in the request, detects
415
+ tool_calls in the streaming response, executes the tools locally, and
416
+ loops back with results until the model produces a final text answer.
417
+ Capped at 20 iterations to prevent infinite loops.
360
418
  """
361
419
  usage = _UsageDelta()
362
420
  base_url = endpoint.base_url.rstrip("/")
363
421
  url = f"{base_url}/chat/completions"
364
422
  model = endpoint.default_model or os.environ.get("SPECSMITH_OPENAI_COMPAT_MODEL", "")
365
423
  if not model:
366
- # The endpoint did not pin a default model and the env override is
367
- # absent. We cannot fabricate one; fall back to the auto-detect chain.
368
424
  return None, usage
369
425
 
370
426
  headers: dict[str, str] = {
@@ -373,23 +429,11 @@ def _run_openai_compat(
373
429
  }
374
430
  try:
375
431
  token = endpoint.resolve_token()
376
- except Exception: # noqa: BLE001 - fall back to auto-detect chain
432
+ except Exception: # noqa: BLE001
377
433
  return None, usage
378
434
  if token:
379
435
  headers["Authorization"] = f"Bearer {token}"
380
436
 
381
- body = json.dumps(
382
- {
383
- "model": model,
384
- "messages": messages,
385
- "stream": True,
386
- # Many vLLM/llama.cpp builds honour OpenAI's stream_options;
387
- # the request is harmless if they don't.
388
- "stream_options": {"include_usage": True},
389
- }
390
- ).encode("utf-8")
391
- req = Request(url, data=body, headers=headers, method="POST") # noqa: S310 - user-supplied
392
-
393
437
  ctx = None
394
438
  if not endpoint.verify_tls and url.startswith("https://"):
395
439
  import ssl
@@ -398,37 +442,151 @@ def _run_openai_compat(
398
442
  ctx.check_hostname = False
399
443
  ctx.verify_mode = ssl.CERT_NONE
400
444
 
401
- pieces: list[str] = []
402
- try:
403
- with urlopen(req, timeout=120, context=ctx) as resp: # noqa: S310 - user-supplied
404
- for raw_line in resp:
405
- line = raw_line.decode("utf-8", errors="replace").rstrip("\n\r")
406
- if not line.startswith("data:"):
407
- continue
408
- payload = line[len("data:") :].strip()
409
- if not payload or payload == "[DONE]":
410
- if payload == "[DONE]":
411
- break
412
- continue
445
+ tool_defs, tool_map = _openai_tool_defs()
446
+ # Use a mutable copy of messages for the tool-call loop
447
+ loop_messages: list[dict[str, Any]] = [dict(m) for m in messages]
448
+ all_text: list[str] = []
449
+ MAX_TOOL_ITERS = 20
450
+
451
+ for _loop in range(MAX_TOOL_ITERS):
452
+ body = json.dumps(
453
+ {
454
+ "model": model,
455
+ "messages": loop_messages,
456
+ "stream": True,
457
+ "stream_options": {"include_usage": True},
458
+ "tools": tool_defs,
459
+ }
460
+ ).encode("utf-8")
461
+ req = Request(url, data=body, headers=headers, method="POST") # noqa: S310
462
+
463
+ pieces: list[str] = []
464
+ # Accumulate tool_calls fragments from the stream
465
+ tc_accum: dict[int, dict[str, str]] = {} # index → {id, name, arguments}
466
+ finish_reason = ""
467
+
468
+ try:
469
+ with urlopen(req, timeout=120, context=ctx) as resp: # noqa: S310
470
+ for raw_line in resp:
471
+ line = raw_line.decode("utf-8", errors="replace").rstrip("\n\r")
472
+ if not line.startswith("data:"):
473
+ continue
474
+ payload = line[len("data:") :].strip()
475
+ if not payload or payload == "[DONE]":
476
+ if payload == "[DONE]":
477
+ break
478
+ continue
479
+ try:
480
+ obj = json.loads(payload)
481
+ except ValueError:
482
+ continue
483
+ choices = obj.get("choices") or []
484
+ usage_obj = obj.get("usage")
485
+ if usage_obj:
486
+ usage.tokens_in += int(usage_obj.get("prompt_tokens") or 0)
487
+ usage.tokens_out += int(usage_obj.get("completion_tokens") or 0)
488
+ if not choices:
489
+ continue
490
+ choice = choices[0] or {}
491
+ delta = choice.get("delta") or {}
492
+ fr = choice.get("finish_reason") or ""
493
+ if fr:
494
+ finish_reason = fr
495
+ # Text content
496
+ chunk = str(delta.get("content") or "")
497
+ if chunk:
498
+ emitter.emit({"type": "llm_chunk", "text": chunk})
499
+ pieces.append(chunk)
500
+ # Tool call fragments
501
+ for tc_delta in delta.get("tool_calls") or []:
502
+ idx = tc_delta.get("index", 0)
503
+ if idx not in tc_accum:
504
+ tc_accum[idx] = {
505
+ "id": tc_delta.get("id", f"call_{idx}"),
506
+ "name": "",
507
+ "arguments": "",
508
+ }
509
+ fn = tc_delta.get("function") or {}
510
+ if fn.get("name"):
511
+ tc_accum[idx]["name"] = fn["name"]
512
+ if fn.get("arguments"):
513
+ tc_accum[idx]["arguments"] += fn["arguments"]
514
+ except (URLError, TimeoutError, OSError):
515
+ return None, usage
516
+
517
+ text_this_turn = "".join(pieces)
518
+ if text_this_turn:
519
+ all_text.append(text_this_turn)
520
+
521
+ # If the model didn't call any tools, we're done
522
+ if finish_reason != "tool_calls" and not tc_accum:
523
+ break
524
+
525
+ # Execute tool calls
526
+ assistant_msg: dict[str, Any] = {
527
+ "role": "assistant",
528
+ "content": text_this_turn or None,
529
+ "tool_calls": [],
530
+ }
531
+ tool_result_msgs: list[dict[str, Any]] = []
532
+
533
+ for idx in sorted(tc_accum):
534
+ tc = tc_accum[idx]
535
+ name = tc["name"]
536
+ call_id = tc["id"]
537
+ try:
538
+ args = json.loads(tc["arguments"]) if tc["arguments"] else {}
539
+ except ValueError:
540
+ args = {}
541
+
542
+ assistant_msg["tool_calls"].append(
543
+ {
544
+ "id": call_id,
545
+ "type": "function",
546
+ "function": {"name": name, "arguments": tc["arguments"]},
547
+ }
548
+ )
549
+
550
+ # Emit tool_started
551
+ emitter.emit({"type": "tool_started", "name": name, "args": args})
552
+
553
+ # Execute
554
+ fn = tool_map.get(name)
555
+ is_error = False
556
+ if fn:
413
557
  try:
414
- obj = json.loads(payload)
415
- except ValueError:
416
- continue
417
- choices = obj.get("choices") or []
418
- usage_obj = obj.get("usage")
419
- if usage_obj:
420
- usage.tokens_in = int(usage_obj.get("prompt_tokens") or 0)
421
- usage.tokens_out = int(usage_obj.get("completion_tokens") or 0)
422
- if not choices:
423
- continue
424
- delta = (choices[0] or {}).get("delta") or {}
425
- chunk = str(delta.get("content") or "")
426
- if chunk:
427
- emitter.token(block_id, chunk)
428
- pieces.append(chunk)
429
- except (URLError, TimeoutError, OSError):
430
- return None, usage
431
- return ("".join(pieces) if pieces else None), usage
558
+ result_text = fn(**args)
559
+ except Exception as exc: # noqa: BLE001
560
+ result_text = f"[ERROR] {exc}"
561
+ is_error = True
562
+ else:
563
+ result_text = f"[Unknown tool: {name}]"
564
+ is_error = True
565
+
566
+ # Emit tool_finished
567
+ emitter.emit(
568
+ {
569
+ "type": "tool_finished",
570
+ "name": name,
571
+ "result": str(result_text)[:4000],
572
+ "is_error": is_error,
573
+ "args": args,
574
+ }
575
+ )
576
+
577
+ tool_result_msgs.append(
578
+ {
579
+ "role": "tool",
580
+ "tool_call_id": call_id,
581
+ "content": str(result_text)[:8000],
582
+ }
583
+ )
584
+
585
+ # Append assistant + tool results and loop for next turn
586
+ loop_messages.append(assistant_msg)
587
+ loop_messages.extend(tool_result_msgs)
588
+
589
+ return ("".join(all_text) if all_text else None), usage
432
590
 
433
591
 
434
592
  def _run_gemini(
@@ -10,9 +10,13 @@ from specsmith.agent.broker import (
10
10
  run_preflight,
11
11
  )
12
12
  from specsmith.agent.orchestrator import Orchestrator
13
+ from specsmith.commands.registry import available_commands
14
+ from specsmith.commands.registry import dispatch as dispatch_command
13
15
 
14
16
  NEXUS_BANNER = "Nexus — Local-first Agentic Development Environment (Specsmith-governed)"
15
17
 
18
+ _REGISTERED_COMMANDS = set(available_commands())
19
+
16
20
 
17
21
  def main():
18
22
  print(NEXUS_BANNER)
@@ -27,7 +31,8 @@ def main():
27
31
 
28
32
  print(
29
33
  "Agents ready. Type plain English to use the natural-language broker, "
30
- "or use slash commands (/plan, /ask, /fix, /why, /exit). "
34
+ "or use slash commands (/plan, /ask, /fix, /test, /review, /context, "
35
+ "/commit, /pr, /undo, /why, /exit). "
31
36
  "Toggle governance details with /why."
32
37
  )
33
38
 
@@ -59,38 +64,11 @@ def main():
59
64
  print(f"Governance details: {state}")
60
65
  continue
61
66
 
62
- if command == "/plan":
63
- if not args:
64
- print("Usage: /plan <task description>")
65
- continue
66
- orchestrator.run_task(f"[PLAN] Create a step-by-step plan for: {args}")
67
-
68
- elif command == "/ask":
69
- if not args:
70
- print("Usage: /ask <question>")
71
- continue
72
- orchestrator.run_task(f"[ASK] Clarify intent and answer the following question: {args}")
73
-
74
- elif command == "/fix":
75
- if not args:
76
- print("Usage: /fix <issue description or file>")
77
- continue
78
- orchestrator.run_task(f"[FIX] Modify code to fix the following issue: {args}")
79
-
80
- elif command == "/test":
81
- orchestrator.run_task(f"[TEST] Run tests for the project. {args}")
82
-
83
- elif command == "/commit":
84
- orchestrator.run_task(f"[COMMIT] Create a git commit for the current changes. {args}")
85
-
86
- elif command == "/pr":
87
- orchestrator.run_task(f"[PR] Prepare a pull request for the current branch. {args}")
88
-
89
- elif command == "/undo":
90
- orchestrator.run_task(f"[UNDO] Revert the last action or commit. {args}")
91
-
92
- elif command == "/context":
93
- orchestrator.run_task(f"[CONTEXT] Show repo knowledge and search context for: {args}")
67
+ # Dispatch registered slash commands through the registry.
68
+ if command in _REGISTERED_COMMANDS:
69
+ cmd_result = dispatch_command(command, args, project_dir, orchestrator)
70
+ print(cmd_result.output)
71
+ continue
94
72
 
95
73
  else:
96
74
  # Default mode (REQ-084 + REQ-086): plain English flows through the
@@ -188,6 +188,13 @@ class AgentRunner:
188
188
  """
189
189
  version = self._package_version()
190
190
  if self.json_events:
191
+ # Report tool count so the VS Code extension can show "(N tools)"
192
+ try:
193
+ from specsmith.agent.tools import AVAILABLE_TOOLS as _AT
194
+
195
+ _tool_count = len(_AT)
196
+ except Exception: # noqa: BLE001
197
+ _tool_count = 0
191
198
  self._emitter.ready(
192
199
  agent="nexus",
193
200
  version=version,
@@ -197,6 +204,7 @@ class AgentRunner:
197
204
  profile_id=self.profile_id or "",
198
205
  capabilities=_capabilities(),
199
206
  endpoint_id=self.endpoint_id or "",
207
+ tools=_tool_count,
200
208
  )
201
209
  else:
202
210
  print(