specsmith 0.8.0.dev237__tar.gz → 0.10.0.dev238__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 (197) hide show
  1. {specsmith-0.8.0.dev237/src/specsmith.egg-info → specsmith-0.10.0.dev238}/PKG-INFO +1 -1
  2. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/pyproject.toml +4 -1
  3. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/__init__.py +1 -1
  4. specsmith-0.10.0.dev238/src/specsmith/agent/core.py +98 -0
  5. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/events.py +58 -0
  6. specsmith-0.10.0.dev238/src/specsmith/agent/fallback.py +142 -0
  7. specsmith-0.10.0.dev238/src/specsmith/agent/profiles.py +569 -0
  8. specsmith-0.10.0.dev238/src/specsmith/agent/runner.py +399 -0
  9. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/cli.py +511 -143
  10. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/vcs_commands.py +1 -1
  11. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238/src/specsmith.egg-info}/PKG-INFO +1 -1
  12. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith.egg-info/SOURCES.txt +7 -1
  13. specsmith-0.10.0.dev238/tests/test_agent_profiles.py +70 -0
  14. specsmith-0.10.0.dev238/tests/test_agent_runner_ready.py +75 -0
  15. specsmith-0.10.0.dev238/tests/test_fallback_chain.py +343 -0
  16. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_phase34_completion.py +4 -36
  17. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_warp_parity.py +4 -115
  18. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_warp_parity_followup.py +0 -95
  19. specsmith-0.8.0.dev237/src/specsmith/cloud_serve.py +0 -150
  20. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/LICENSE +0 -0
  21. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/README.md +0 -0
  22. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/setup.cfg +0 -0
  23. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/__init__.py +0 -0
  24. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/belief.py +0 -0
  25. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/certainty.py +0 -0
  26. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/failure_graph.py +0 -0
  27. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/py.typed +0 -0
  28. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/recovery.py +0 -0
  29. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/session.py +0 -0
  30. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/stress_tester.py +0 -0
  31. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/epistemic/trace.py +0 -0
  32. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/__main__.py +0 -0
  33. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/__init__.py +0 -0
  34. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/broker.py +0 -0
  35. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/chat_runner.py +0 -0
  36. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/cleanup.py +0 -0
  37. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/endpoints.py +0 -0
  38. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/indexer.py +0 -0
  39. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/mcp.py +0 -0
  40. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/memory.py +0 -0
  41. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/orchestrator.py +0 -0
  42. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/repl.py +0 -0
  43. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/router.py +0 -0
  44. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/rules.py +0 -0
  45. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/safety.py +0 -0
  46. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/suggester.py +0 -0
  47. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/tools.py +0 -0
  48. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/verifier.py +0 -0
  49. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/agent/voice.py +0 -0
  50. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/architect.py +0 -0
  51. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/auditor.py +0 -0
  52. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/auth.py +0 -0
  53. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/block_export.py +0 -0
  54. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/commands/__init__.py +0 -0
  55. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/compressor.py +0 -0
  56. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/config.py +0 -0
  57. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/console_utils.py +0 -0
  58. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/credit_analyzer.py +0 -0
  59. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/credits.py +0 -0
  60. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/differ.py +0 -0
  61. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/doctor.py +0 -0
  62. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/drive.py +0 -0
  63. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/epistemic/__init__.py +0 -0
  64. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/epistemic/belief.py +0 -0
  65. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/epistemic/certainty.py +0 -0
  66. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/epistemic/failure_graph.py +0 -0
  67. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/epistemic/recovery.py +0 -0
  68. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/epistemic/stress_tester.py +0 -0
  69. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/executor.py +0 -0
  70. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/exporter.py +0 -0
  71. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/__init__.py +0 -0
  72. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/app.py +0 -0
  73. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/main_window.py +0 -0
  74. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/session_tab.py +0 -0
  75. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/theme.py +0 -0
  76. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/widgets/__init__.py +0 -0
  77. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/widgets/chat_view.py +0 -0
  78. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/widgets/input_bar.py +0 -0
  79. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/widgets/provider_bar.py +0 -0
  80. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/widgets/token_meter.py +0 -0
  81. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/widgets/tool_panel.py +0 -0
  82. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/widgets/update_checker.py +0 -0
  83. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/gui/worker.py +0 -0
  84. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/history_search.py +0 -0
  85. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/importer.py +0 -0
  86. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/__init__.py +0 -0
  87. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/agent_skill.py +0 -0
  88. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/aider.py +0 -0
  89. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/base.py +0 -0
  90. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/claude_code.py +0 -0
  91. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/copilot.py +0 -0
  92. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/cursor.py +0 -0
  93. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/gemini.py +0 -0
  94. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/integrations/windsurf.py +0 -0
  95. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/languages.py +0 -0
  96. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/ledger.py +0 -0
  97. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/patent.py +0 -0
  98. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/phase.py +0 -0
  99. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/plugins.py +0 -0
  100. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/profiles.py +0 -0
  101. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/rate_limits.py +0 -0
  102. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/releaser.py +0 -0
  103. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/requirements.py +0 -0
  104. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/requirements_parser.py +0 -0
  105. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/retrieval.py +0 -0
  106. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/scaffolder.py +0 -0
  107. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/serve.py +0 -0
  108. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/session.py +0 -0
  109. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/skills.py +0 -0
  110. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/agents.md.j2 +0 -0
  111. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/bug_report.md.j2 +0 -0
  112. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/code_of_conduct.md.j2 +0 -0
  113. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/contributing.md.j2 +0 -0
  114. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/feature_request.md.j2 +0 -0
  115. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/license-Apache-2.0.j2 +0 -0
  116. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/license-MIT.j2 +0 -0
  117. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/pull_request_template.md.j2 +0 -0
  118. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/community/security.md.j2 +0 -0
  119. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
  120. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/docs/mkdocs.yml.j2 +0 -0
  121. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/docs/readthedocs.yaml.j2 +0 -0
  122. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
  123. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
  124. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/editorconfig.j2 +0 -0
  125. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/gitattributes.j2 +0 -0
  126. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/gitignore.j2 +0 -0
  127. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/go/go.mod.j2 +0 -0
  128. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/go/main.go.j2 +0 -0
  129. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/belief-registry.md.j2 +0 -0
  130. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
  131. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
  132. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/epistemic-axioms.md.j2 +0 -0
  133. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/failure-modes.md.j2 +0 -0
  134. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/lifecycle.md.j2 +0 -0
  135. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/roles.md.j2 +0 -0
  136. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/rules.md.j2 +0 -0
  137. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/session-protocol.md.j2 +0 -0
  138. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/uncertainty-map.md.j2 +0 -0
  139. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/governance/verification.md.j2 +0 -0
  140. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/js/package.json.j2 +0 -0
  141. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/ledger.md.j2 +0 -0
  142. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/python/cli.py.j2 +0 -0
  143. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/python/init.py.j2 +0 -0
  144. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/python/pyproject.toml.j2 +0 -0
  145. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/readme.md.j2 +0 -0
  146. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/rust/Cargo.toml.j2 +0 -0
  147. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/rust/main.rs.j2 +0 -0
  148. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
  149. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
  150. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
  151. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
  152. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
  153. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
  154. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/templates/workflows/release.yml.j2 +0 -0
  155. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/tool_installer.py +0 -0
  156. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/toolrules.py +0 -0
  157. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/tools.py +0 -0
  158. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/trace.py +0 -0
  159. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/updater.py +0 -0
  160. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/upgrader.py +0 -0
  161. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/validator.py +0 -0
  162. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/vcs/__init__.py +0 -0
  163. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/vcs/base.py +0 -0
  164. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/vcs/bitbucket.py +0 -0
  165. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/vcs/github.py +0 -0
  166. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/vcs/gitlab.py +0 -0
  167. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/wireframes.py +0 -0
  168. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith/workspace.py +0 -0
  169. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith.egg-info/dependency_links.txt +0 -0
  170. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith.egg-info/entry_points.txt +0 -0
  171. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith.egg-info/requires.txt +0 -0
  172. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/src/specsmith.egg-info/top_level.txt +0 -0
  173. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_CMD_001.py +0 -0
  174. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_auditor.py +0 -0
  175. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_chat_diff_decision.py +0 -0
  176. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_chat_runner_openai_compat.py +0 -0
  177. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_chat_stdin_protocol.py +0 -0
  178. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_cli.py +0 -0
  179. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_cli_workflows_history_drive.py +0 -0
  180. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_compressor.py +0 -0
  181. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_e2e_nexus.py +0 -0
  182. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_endpoints_cli.py +0 -0
  183. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_endpoints_store.py +0 -0
  184. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_epistemic.py +0 -0
  185. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_importer.py +0 -0
  186. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_integrations.py +0 -0
  187. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_mcp_client.py +0 -0
  188. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_nexus.py +0 -0
  189. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_phase1_4_new.py +0 -0
  190. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_rate_limits.py +0 -0
  191. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_scaffolder.py +0 -0
  192. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_skill_marketplace.py +0 -0
  193. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_smoke.py +0 -0
  194. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_suggester.py +0 -0
  195. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_tools.py +0 -0
  196. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_validator.py +0 -0
  197. {specsmith-0.8.0.dev237 → specsmith-0.10.0.dev238}/tests/test_vcs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specsmith
3
- Version: 0.8.0.dev237
3
+ Version: 0.10.0.dev238
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "specsmith"
7
- version = "0.8.0.dev237"
7
+ version = "0.10.0.dev238"
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"
@@ -171,6 +171,9 @@ module = [
171
171
  "specsmith.importer",
172
172
  "specsmith.agent.providers.gemini",
173
173
  "specsmith.agent.runner",
174
+ "specsmith.agent.profiles",
175
+ "specsmith.agent.fallback",
176
+ "specsmith.agent.core",
174
177
  "specsmith.agent.cleanup",
175
178
  "specsmith.agent.orchestrator",
176
179
  "specsmith.agent.repl",
@@ -8,4 +8,4 @@ from importlib.metadata import version as _pkg_version
8
8
  try:
9
9
  __version__: str = _pkg_version("specsmith")
10
10
  except PackageNotFoundError: # running from source without install
11
- __version__ = "0.3.6" # fallback: keep in sync with pyproject.toml
11
+ __version__ = "0.10.0" # fallback: keep in sync with pyproject.toml
@@ -0,0 +1,98 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
3
+ """Shared agent runtime primitives (REQ-145).
4
+
5
+ Hosts low-level enums and dataclasses that span :mod:`specsmith.agent.runner`,
6
+ :mod:`specsmith.serve`, :mod:`specsmith.agent.profiles`, and
7
+ :mod:`specsmith.agent.fallback` without forcing them to import each other.
8
+
9
+ The historical ``cli.py`` referenced ``ModelTier`` from this module before
10
+ it existed in the source tree (the file was lost in an earlier refactor),
11
+ which produced an ``ImportError`` the moment ``specsmith run`` was
12
+ invoked. Restoring the symbol here is the prerequisite for the bridge
13
+ ``ready`` event handshake to land before the VS Code extension's 20 s
14
+ startup timeout fires.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import enum
20
+ from dataclasses import dataclass, field
21
+ from typing import Any
22
+
23
+
24
+ class ModelTier(str, enum.Enum):
25
+ """Capability tier for an LLM call.
26
+
27
+ Ordered cheapest → most capable so that a fallback chain can iterate
28
+ in declaration order without external metadata.
29
+ """
30
+
31
+ FAST = "fast"
32
+ BALANCED = "balanced"
33
+ POWERFUL = "powerful"
34
+
35
+ @classmethod
36
+ def parse(
37
+ cls,
38
+ value: str | ModelTier | None,
39
+ default: ModelTier | None = None,
40
+ ) -> ModelTier:
41
+ """Tolerant parser used by CLI option handlers."""
42
+ if value is None or value == "":
43
+ return default or cls.BALANCED
44
+ if isinstance(value, cls):
45
+ return value
46
+ try:
47
+ return cls(str(value).strip().lower())
48
+ except ValueError:
49
+ return default or cls.BALANCED
50
+
51
+
52
+ @dataclass
53
+ class AgentState:
54
+ """Mutable per-session metrics surfaced via ``specsmith serve``'s
55
+ ``GET /api/status`` endpoint and the VS Code TokenMeter chip.
56
+
57
+ Field names mirror what :class:`specsmith.serve._AgentThread` reads off
58
+ ``runner._state``; do not rename without updating that consumer.
59
+ """
60
+
61
+ provider_name: str = ""
62
+ model_name: str = ""
63
+ profile_id: str = ""
64
+ session_tokens: int = 0
65
+ tokens_in: int = 0
66
+ tokens_out: int = 0
67
+ total_cost_usd: float = 0.0
68
+ tool_calls_made: int = 0
69
+ elapsed_minutes: float = 0.0
70
+ by_profile: dict[str, dict[str, Any]] = field(default_factory=dict)
71
+
72
+ def credit(
73
+ self,
74
+ *,
75
+ profile_id: str,
76
+ tokens_in: int = 0,
77
+ tokens_out: int = 0,
78
+ cost_usd: float = 0.0,
79
+ tool_calls: int = 0,
80
+ ) -> None:
81
+ """Aggregate one turn's metrics into the running totals."""
82
+ self.tokens_in += int(tokens_in)
83
+ self.tokens_out += int(tokens_out)
84
+ self.session_tokens = self.tokens_in + self.tokens_out
85
+ self.total_cost_usd += float(cost_usd)
86
+ self.tool_calls_made += int(tool_calls)
87
+ bucket = self.by_profile.setdefault(
88
+ profile_id or "(default)",
89
+ {"tokens_in": 0, "tokens_out": 0, "cost_usd": 0.0, "tool_calls": 0, "turns": 0},
90
+ )
91
+ bucket["tokens_in"] += int(tokens_in)
92
+ bucket["tokens_out"] += int(tokens_out)
93
+ bucket["cost_usd"] = round(bucket["cost_usd"] + float(cost_usd), 6)
94
+ bucket["tool_calls"] += int(tool_calls)
95
+ bucket["turns"] += 1
96
+
97
+
98
+ __all__ = ["AgentState", "ModelTier"]
@@ -19,6 +19,9 @@ Event kinds
19
19
  * ``plan_step`` - status transition for a step in the active plan
20
20
  block (REQ-114).
21
21
  * ``task_complete`` - final block; carries final summary + profile.
22
+ * ``ready`` - emitted exactly once at process start (REQ-145);
23
+ the VS Code bridge waits up to 20 s for this
24
+ frame before declaring the agent unresponsive.
22
25
  """
23
26
 
24
27
  from __future__ import annotations
@@ -58,6 +61,61 @@ class EventEmitter:
58
61
  with contextlib.suppress(Exception):
59
62
  self.stream.flush()
60
63
 
64
+ # ── Lifecycle helpers ────────────────────────────────────────────────
65
+
66
+ def ready(
67
+ self,
68
+ *,
69
+ agent: str = "nexus",
70
+ version: str = "",
71
+ project_dir: str = "",
72
+ provider: str = "",
73
+ model: str = "",
74
+ profile_id: str = "",
75
+ capabilities: list[str] | None = None,
76
+ **extra: Any,
77
+ ) -> None:
78
+ """Emit the bridge handshake frame (REQ-145).
79
+
80
+ The VS Code extension's :class:`SpecsmithBridge` keys off this
81
+ single event to flip from ``starting`` → ``waiting`` and to start
82
+ flushing the queued user prompts. Schema is intentionally flat so
83
+ a ``JSON.parse`` line check is enough on the consumer side.
84
+ """
85
+ payload: dict[str, Any] = {
86
+ "type": "ready",
87
+ "timestamp": _now_iso(),
88
+ "agent": agent,
89
+ "version": version,
90
+ "project_dir": project_dir,
91
+ "provider": provider,
92
+ "model": model,
93
+ "profile_id": profile_id,
94
+ "capabilities": list(capabilities or []),
95
+ }
96
+ payload.update(extra)
97
+ self.emit(payload)
98
+
99
+ def system(self, message: str, **extra: Any) -> None:
100
+ """Emit a free-form system note (matches bridge.ts handler)."""
101
+ self.emit({"type": "system", "message": message, **extra})
102
+
103
+ def turn_done(self, **extra: Any) -> None:
104
+ """Emit the per-turn terminator the bridge uses to clear timers."""
105
+ self.emit({"type": "turn_done", "timestamp": _now_iso(), **extra})
106
+
107
+ def error(self, message: str, *, recoverable: bool = False, **extra: Any) -> None:
108
+ """Emit an error frame (recoverable = retry will be offered)."""
109
+ self.emit(
110
+ {
111
+ "type": "error",
112
+ "timestamp": _now_iso(),
113
+ "message": message,
114
+ "recoverable": bool(recoverable),
115
+ **extra,
116
+ }
117
+ )
118
+
61
119
  # ── Block helpers ────────────────────────────────────────────────────
62
120
 
63
121
  def block_start(self, kind: str, *, agent: str = "nexus", **payload: Any) -> str:
@@ -0,0 +1,142 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
3
+ """Resilient fallback-chain executor for agent profiles (REQ-146).
4
+
5
+ Profiles in :mod:`specsmith.agent.profiles` carry a ``fallback_chain``
6
+ list of ``"<provider>/<model>"`` or ``"endpoint:<id>"`` strings. When the
7
+ primary call raises a transient error (timeout / connection refused /
8
+ HTTP 429 / HTTP 5xx), this module walks the chain in order until one
9
+ returns successfully or the chain is exhausted.
10
+
11
+ The chain is **resilience**, not **routing** — picking the right primary
12
+ is the routing table's job. The chain only kicks in when the chosen
13
+ primary fails.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import contextlib
19
+ import socket
20
+ from collections.abc import Callable, Iterable
21
+ from dataclasses import dataclass
22
+ from typing import Any
23
+ from urllib.error import HTTPError, URLError
24
+
25
+ # (ruff I001 sentinel: imports above are intentionally grouped stdlib + typing)
26
+
27
+
28
+ # Errors we treat as worth falling through. Anything else is a programmer
29
+ # bug and should bubble up so we don't paper over correctness issues.
30
+ TRANSIENT_EXCEPTIONS: tuple[type[BaseException], ...] = (
31
+ TimeoutError,
32
+ socket.timeout,
33
+ URLError,
34
+ ConnectionError,
35
+ OSError,
36
+ )
37
+
38
+
39
+ @dataclass
40
+ class FallbackAttempt:
41
+ """One step of an executed chain."""
42
+
43
+ target: str
44
+ ok: bool
45
+ error: str = ""
46
+
47
+ def to_dict(self) -> dict[str, Any]:
48
+ return {"target": self.target, "ok": self.ok, "error": self.error}
49
+
50
+
51
+ @dataclass
52
+ class FallbackResult:
53
+ """Outcome of :func:`run_with_fallback`."""
54
+
55
+ value: Any
56
+ used: str = ""
57
+ attempts: list[FallbackAttempt] = None # type: ignore[assignment]
58
+
59
+ def __post_init__(self) -> None:
60
+ if self.attempts is None:
61
+ self.attempts = []
62
+
63
+
64
+ def _is_transient(exc: BaseException) -> bool:
65
+ if isinstance(exc, HTTPError):
66
+ return 500 <= int(getattr(exc, "code", 0) or 0) < 600 or exc.code in {408, 429}
67
+ return isinstance(exc, TRANSIENT_EXCEPTIONS)
68
+
69
+
70
+ def parse_target(target: str) -> tuple[str, str, str]:
71
+ """Decompose a chain entry into ``(kind, provider_or_id, model)``.
72
+
73
+ Examples::
74
+
75
+ parse_target("anthropic/claude-haiku-4-5")
76
+ # -> ("provider", "anthropic", "claude-haiku-4-5")
77
+ parse_target("ollama/qwen2.5:7b")
78
+ # -> ("provider", "ollama", "qwen2.5:7b")
79
+ parse_target("endpoint:home-vllm")
80
+ # -> ("endpoint", "home-vllm", "")
81
+ """
82
+ cleaned = (target or "").strip()
83
+ if not cleaned:
84
+ return ("provider", "", "")
85
+ if cleaned.startswith("endpoint:"):
86
+ return ("endpoint", cleaned[len("endpoint:") :], "")
87
+ if "/" not in cleaned:
88
+ return ("provider", cleaned, "")
89
+ provider, _, model = cleaned.partition("/")
90
+ return ("provider", provider.strip(), model.strip())
91
+
92
+
93
+ def run_with_fallback(
94
+ primary_target: str,
95
+ fallback_chain: Iterable[str],
96
+ invoke: Callable[[str, str, str], Any],
97
+ *,
98
+ on_attempt: Callable[[FallbackAttempt], None] | None = None,
99
+ ) -> FallbackResult:
100
+ """Try the primary target; on transient failure walk the chain.
101
+
102
+ ``invoke`` is called as ``invoke(kind, provider_or_id, model)`` and
103
+ must raise on failure. Any non-transient exception aborts the chain
104
+ immediately (we don't want to mask a programmer bug as an outage).
105
+ """
106
+ targets = [primary_target] + [t for t in fallback_chain if t]
107
+ result = FallbackResult(value=None, attempts=[])
108
+ for target in targets:
109
+ kind, ident, model = parse_target(target)
110
+ if not ident:
111
+ continue
112
+ try:
113
+ value = invoke(kind, ident, model)
114
+ except Exception as exc: # noqa: BLE001
115
+ attempt = FallbackAttempt(target=target, ok=False, error=str(exc))
116
+ result.attempts.append(attempt)
117
+ if on_attempt:
118
+ with contextlib.suppress(Exception):
119
+ on_attempt(attempt)
120
+ if not _is_transient(exc):
121
+ # Programmer error / auth failure — bubble up immediately
122
+ # so the caller sees the real cause.
123
+ raise
124
+ continue
125
+ attempt = FallbackAttempt(target=target, ok=True)
126
+ result.attempts.append(attempt)
127
+ if on_attempt:
128
+ with contextlib.suppress(Exception):
129
+ on_attempt(attempt)
130
+ result.value = value
131
+ result.used = target
132
+ return result
133
+ return result # exhausted, value=None
134
+
135
+
136
+ __all__ = [
137
+ "FallbackAttempt",
138
+ "FallbackResult",
139
+ "TRANSIENT_EXCEPTIONS",
140
+ "parse_target",
141
+ "run_with_fallback",
142
+ ]