specsmith 0.3.10.dev198__tar.gz → 0.3.10.dev210__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 (179) hide show
  1. {specsmith-0.3.10.dev198/src/specsmith.egg-info → specsmith-0.3.10.dev210}/PKG-INFO +40 -2
  2. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/README.md +37 -1
  3. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/pyproject.toml +5 -1
  4. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/providers/ollama.py +5 -2
  5. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/runner.py +10 -0
  6. specsmith-0.3.10.dev210/src/specsmith/agents/__init__.py +7 -0
  7. specsmith-0.3.10.dev210/src/specsmith/agents/cli.py +233 -0
  8. specsmith-0.3.10.dev210/src/specsmith/agents/config.py +74 -0
  9. specsmith-0.3.10.dev210/src/specsmith/agents/reports.py +77 -0
  10. specsmith-0.3.10.dev210/src/specsmith/agents/roles.py +208 -0
  11. specsmith-0.3.10.dev210/src/specsmith/agents/tools/__init__.py +34 -0
  12. specsmith-0.3.10.dev210/src/specsmith/agents/tools/filesystem.py +187 -0
  13. specsmith-0.3.10.dev210/src/specsmith/agents/tools/git.py +70 -0
  14. specsmith-0.3.10.dev210/src/specsmith/agents/tools/shell.py +69 -0
  15. specsmith-0.3.10.dev210/src/specsmith/agents/tools/tests.py +59 -0
  16. specsmith-0.3.10.dev210/src/specsmith/agents/workflows/__init__.py +3 -0
  17. specsmith-0.3.10.dev210/src/specsmith/agents/workflows/improve.py +179 -0
  18. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/auditor.py +67 -0
  19. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/cli.py +12 -0
  20. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210/src/specsmith.egg-info}/PKG-INFO +40 -2
  21. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith.egg-info/SOURCES.txt +13 -0
  22. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith.egg-info/requires.txt +3 -0
  23. specsmith-0.3.10.dev210/tests/test_agent.py +293 -0
  24. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/LICENSE +0 -0
  25. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/setup.cfg +0 -0
  26. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/epistemic/__init__.py +0 -0
  27. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/epistemic/belief.py +0 -0
  28. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/epistemic/certainty.py +0 -0
  29. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/epistemic/failure_graph.py +0 -0
  30. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/epistemic/py.typed +0 -0
  31. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/epistemic/recovery.py +0 -0
  32. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/epistemic/session.py +0 -0
  33. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/epistemic/stress_tester.py +0 -0
  34. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/epistemic/trace.py +0 -0
  35. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/__init__.py +0 -0
  36. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/__main__.py +0 -0
  37. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/__init__.py +0 -0
  38. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/core.py +0 -0
  39. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/hooks.py +0 -0
  40. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/optimizer.py +0 -0
  41. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/profiles/epistemic-auditor.md +0 -0
  42. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/profiles/planner.md +0 -0
  43. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/profiles/verifier.md +0 -0
  44. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/providers/__init__.py +0 -0
  45. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/providers/anthropic.py +0 -0
  46. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/providers/gemini.py +0 -0
  47. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/providers/mistral.py +0 -0
  48. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/providers/openai.py +0 -0
  49. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/skills.py +0 -0
  50. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/agent/tools.py +0 -0
  51. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/architect.py +0 -0
  52. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/auth.py +0 -0
  53. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/commands/__init__.py +0 -0
  54. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/compressor.py +0 -0
  55. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/config.py +0 -0
  56. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/credit_analyzer.py +0 -0
  57. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/credits.py +0 -0
  58. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/differ.py +0 -0
  59. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/doctor.py +0 -0
  60. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/epistemic/__init__.py +0 -0
  61. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/epistemic/belief.py +0 -0
  62. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/epistemic/certainty.py +0 -0
  63. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/epistemic/failure_graph.py +0 -0
  64. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/epistemic/recovery.py +0 -0
  65. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/epistemic/stress_tester.py +0 -0
  66. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/executor.py +0 -0
  67. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/exporter.py +0 -0
  68. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/gui/__init__.py +0 -0
  69. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/gui/app.py +0 -0
  70. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/gui/main_window.py +0 -0
  71. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/gui/session_tab.py +0 -0
  72. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/gui/theme.py +0 -0
  73. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/gui/widgets/__init__.py +0 -0
  74. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/gui/widgets/chat_view.py +0 -0
  75. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/gui/widgets/input_bar.py +0 -0
  76. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/gui/widgets/provider_bar.py +0 -0
  77. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/gui/widgets/token_meter.py +0 -0
  78. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/gui/widgets/tool_panel.py +0 -0
  79. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/gui/widgets/update_checker.py +0 -0
  80. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/gui/worker.py +0 -0
  81. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/importer.py +0 -0
  82. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/integrations/__init__.py +0 -0
  83. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/integrations/aider.py +0 -0
  84. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/integrations/base.py +0 -0
  85. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/integrations/claude_code.py +0 -0
  86. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/integrations/copilot.py +0 -0
  87. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/integrations/cursor.py +0 -0
  88. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/integrations/gemini.py +0 -0
  89. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/integrations/warp.py +0 -0
  90. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/integrations/windsurf.py +0 -0
  91. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/languages.py +0 -0
  92. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/ledger.py +0 -0
  93. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/ollama_cmds.py +0 -0
  94. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/patent.py +0 -0
  95. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/phase.py +0 -0
  96. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/plugins.py +0 -0
  97. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/profiles.py +0 -0
  98. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/rate_limits.py +0 -0
  99. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/releaser.py +0 -0
  100. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/requirements.py +0 -0
  101. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/retrieval.py +0 -0
  102. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/scaffolder.py +0 -0
  103. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/session.py +0 -0
  104. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/agents.md.j2 +0 -0
  105. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/community/bug_report.md.j2 +0 -0
  106. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/community/code_of_conduct.md.j2 +0 -0
  107. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/community/contributing.md.j2 +0 -0
  108. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/community/feature_request.md.j2 +0 -0
  109. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/community/license-Apache-2.0.j2 +0 -0
  110. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/community/license-MIT.j2 +0 -0
  111. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/community/pull_request_template.md.j2 +0 -0
  112. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/community/security.md.j2 +0 -0
  113. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
  114. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/docs/mkdocs.yml.j2 +0 -0
  115. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/docs/readthedocs.yaml.j2 +0 -0
  116. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
  117. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
  118. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/editorconfig.j2 +0 -0
  119. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/gitattributes.j2 +0 -0
  120. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/gitignore.j2 +0 -0
  121. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/go/go.mod.j2 +0 -0
  122. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/go/main.go.j2 +0 -0
  123. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/governance/belief-registry.md.j2 +0 -0
  124. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
  125. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
  126. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/governance/epistemic-axioms.md.j2 +0 -0
  127. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/governance/failure-modes.md.j2 +0 -0
  128. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/governance/lifecycle.md.j2 +0 -0
  129. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/governance/roles.md.j2 +0 -0
  130. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/governance/rules.md.j2 +0 -0
  131. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/governance/session-protocol.md.j2 +0 -0
  132. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/governance/uncertainty-map.md.j2 +0 -0
  133. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/governance/verification.md.j2 +0 -0
  134. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/js/package.json.j2 +0 -0
  135. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/ledger.md.j2 +0 -0
  136. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/python/cli.py.j2 +0 -0
  137. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/python/init.py.j2 +0 -0
  138. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/python/pyproject.toml.j2 +0 -0
  139. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/readme.md.j2 +0 -0
  140. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/rust/Cargo.toml.j2 +0 -0
  141. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/rust/main.rs.j2 +0 -0
  142. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
  143. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
  144. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
  145. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
  146. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
  147. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
  148. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/templates/workflows/release.yml.j2 +0 -0
  149. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/tool_installer.py +0 -0
  150. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/toolrules.py +0 -0
  151. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/tools.py +0 -0
  152. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/trace.py +0 -0
  153. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/updater.py +0 -0
  154. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/upgrader.py +0 -0
  155. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/validator.py +0 -0
  156. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/vcs/__init__.py +0 -0
  157. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/vcs/base.py +0 -0
  158. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/vcs/bitbucket.py +0 -0
  159. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/vcs/github.py +0 -0
  160. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/vcs/gitlab.py +0 -0
  161. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/vcs_commands.py +0 -0
  162. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/wireframes.py +0 -0
  163. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith/workspace.py +0 -0
  164. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith.egg-info/dependency_links.txt +0 -0
  165. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith.egg-info/entry_points.txt +0 -0
  166. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/src/specsmith.egg-info/top_level.txt +0 -0
  167. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/tests/test_auditor.py +0 -0
  168. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/tests/test_cli.py +0 -0
  169. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/tests/test_compressor.py +0 -0
  170. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/tests/test_epistemic.py +0 -0
  171. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/tests/test_importer.py +0 -0
  172. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/tests/test_integrations.py +0 -0
  173. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/tests/test_optimizer.py +0 -0
  174. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/tests/test_rate_limits.py +0 -0
  175. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/tests/test_scaffolder.py +0 -0
  176. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/tests/test_smoke.py +0 -0
  177. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/tests/test_tools.py +0 -0
  178. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/tests/test_validator.py +0 -0
  179. {specsmith-0.3.10.dev198 → specsmith-0.3.10.dev210}/tests/test_vcs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specsmith
3
- Version: 0.3.10.dev198
3
+ Version: 0.3.10.dev210
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
@@ -51,6 +51,8 @@ Provides-Extra: mistral
51
51
  Requires-Dist: openai>=1.0; extra == "mistral"
52
52
  Provides-Extra: gui
53
53
  Requires-Dist: PySide6>=6.6; extra == "gui"
54
+ Provides-Extra: ag2
55
+ Requires-Dist: ag2[ollama]; extra == "ag2"
54
56
  Provides-Extra: agent
55
57
  Requires-Dist: anthropic>=0.56; extra == "agent"
56
58
  Requires-Dist: openai>=1.0; extra == "agent"
@@ -186,12 +188,45 @@ specsmith epistemic-audit --project-dir ./my-project
186
188
  # Start the agentic REPL
187
189
  specsmith run --project-dir ./my-project
188
190
 
191
+ # AG2 agent shell — Planner/Builder/Verifier over Ollama
192
+ specsmith agent status # check agent config + Ollama
193
+ specsmith agent plan "add logging" # plan only (no execution)
194
+ specsmith agent run "fix lint errors" # full Plan → Build → Verify
195
+ specsmith agent improve "add tests" # self-improvement with reports
196
+ specsmith agent verify # run Verifier on current state
197
+ specsmith agent reports # list improvement reports
198
+
189
199
  # Check current AEE workflow phase
190
200
  specsmith phase --project-dir ./my-project
191
201
  ```
192
202
 
193
203
  ---
194
204
 
205
+ ## AG2 Agent Shell — Local AI Agents over Ollama
206
+
207
+ specsmith includes an AG2-based agent shell with three specialized agents:
208
+
209
+ - **Planner** — inspects repo, generates execution plans with acceptance criteria
210
+ - **Builder** — makes code/doc changes following the plan
211
+ - **Verifier** — runs tests, accepts or rejects changes
212
+
213
+ All agents run locally on Ollama (default: `qwen2.5:14b`). Zero cloud cost.
214
+
215
+ ```bash
216
+ pip install "specsmith[ag2]" # install AG2 + Ollama support
217
+ specsmith agent status # verify config + Ollama running
218
+ specsmith agent run "fix lint errors" # Plan → Build → Verify pipeline
219
+ specsmith agent improve "add tests for config.py" # self-improvement with reports
220
+ ```
221
+
222
+ The agent shell stores structured reports at `.specsmith/agent-reports/` with task ID,
223
+ files changed, test results, verdict (ACCEPT/REJECT), and follow-up tasks.
224
+
225
+ Configurable per-project in `scaffold.yml` under `agents:` or via the VS Code
226
+ Project Settings → Agent tab.
227
+
228
+ ---
229
+
195
230
  ## VS Code Extension
196
231
 
197
232
  The **specsmith AEE Workbench** VS Code extension is the flagship client:
@@ -211,7 +246,10 @@ The **specsmith AEE Workbench** VS Code extension is the flagship client:
211
246
  - **Execution profiles** — safe / standard / open / admin; custom allow/block command lists
212
247
  - **AEE phase indicator** — shows current phase with readiness %, Next Phase button, phase selector
213
248
  - **AI agent sessions** — independent process per project, JSONL bridge, chat with file injection
249
+ - **AG2 agent shell** — Planner/Builder/Verifier agents over Ollama in Actions tab
250
+ - **Agent tab** — per-project provider/model/context/iteration config (overrides global defaults)
214
251
  - **Live model listing** — Anthropic, OpenAI, Gemini, Mistral, local Ollama (GPU-aware)
252
+ - **Ollama model catalog** — 16 models, 4 tiers, GPU-aware recommendations, filter by installed/available
215
253
  - **Ollama integration** — model manager (update/remove/update-all), version check, upgrade
216
254
  - **FPGA/HDL tool support** — vivado, gtkwave, vsg, ghdl, verilator, yosys, nextpnr, and 15 more
217
255
  - **Tool installer** — scan installed tools; one-click install via winget/brew/apt for missing tools
@@ -284,7 +322,7 @@ Supported tools: **Synthesis:** vivado, quartus, radiant, diamond, gowin.
284
322
 
285
323
  **Workflow:** `phase show/set/next/list` `ledger add/list` `req list/add/gaps/trace`
286
324
 
287
- **Agent:** `run` `agent providers/tools/skills`
325
+ **Agent:** `run` `agent run/plan/status/verify/improve/reports` `agent providers/tools/skills`
288
326
 
289
327
  **Ollama:** `ollama list/available/gpu/pull/suggest`
290
328
 
@@ -121,12 +121,45 @@ specsmith epistemic-audit --project-dir ./my-project
121
121
  # Start the agentic REPL
122
122
  specsmith run --project-dir ./my-project
123
123
 
124
+ # AG2 agent shell — Planner/Builder/Verifier over Ollama
125
+ specsmith agent status # check agent config + Ollama
126
+ specsmith agent plan "add logging" # plan only (no execution)
127
+ specsmith agent run "fix lint errors" # full Plan → Build → Verify
128
+ specsmith agent improve "add tests" # self-improvement with reports
129
+ specsmith agent verify # run Verifier on current state
130
+ specsmith agent reports # list improvement reports
131
+
124
132
  # Check current AEE workflow phase
125
133
  specsmith phase --project-dir ./my-project
126
134
  ```
127
135
 
128
136
  ---
129
137
 
138
+ ## AG2 Agent Shell — Local AI Agents over Ollama
139
+
140
+ specsmith includes an AG2-based agent shell with three specialized agents:
141
+
142
+ - **Planner** — inspects repo, generates execution plans with acceptance criteria
143
+ - **Builder** — makes code/doc changes following the plan
144
+ - **Verifier** — runs tests, accepts or rejects changes
145
+
146
+ All agents run locally on Ollama (default: `qwen2.5:14b`). Zero cloud cost.
147
+
148
+ ```bash
149
+ pip install "specsmith[ag2]" # install AG2 + Ollama support
150
+ specsmith agent status # verify config + Ollama running
151
+ specsmith agent run "fix lint errors" # Plan → Build → Verify pipeline
152
+ specsmith agent improve "add tests for config.py" # self-improvement with reports
153
+ ```
154
+
155
+ The agent shell stores structured reports at `.specsmith/agent-reports/` with task ID,
156
+ files changed, test results, verdict (ACCEPT/REJECT), and follow-up tasks.
157
+
158
+ Configurable per-project in `scaffold.yml` under `agents:` or via the VS Code
159
+ Project Settings → Agent tab.
160
+
161
+ ---
162
+
130
163
  ## VS Code Extension
131
164
 
132
165
  The **specsmith AEE Workbench** VS Code extension is the flagship client:
@@ -146,7 +179,10 @@ The **specsmith AEE Workbench** VS Code extension is the flagship client:
146
179
  - **Execution profiles** — safe / standard / open / admin; custom allow/block command lists
147
180
  - **AEE phase indicator** — shows current phase with readiness %, Next Phase button, phase selector
148
181
  - **AI agent sessions** — independent process per project, JSONL bridge, chat with file injection
182
+ - **AG2 agent shell** — Planner/Builder/Verifier agents over Ollama in Actions tab
183
+ - **Agent tab** — per-project provider/model/context/iteration config (overrides global defaults)
149
184
  - **Live model listing** — Anthropic, OpenAI, Gemini, Mistral, local Ollama (GPU-aware)
185
+ - **Ollama model catalog** — 16 models, 4 tiers, GPU-aware recommendations, filter by installed/available
150
186
  - **Ollama integration** — model manager (update/remove/update-all), version check, upgrade
151
187
  - **FPGA/HDL tool support** — vivado, gtkwave, vsg, ghdl, verilator, yosys, nextpnr, and 15 more
152
188
  - **Tool installer** — scan installed tools; one-click install via winget/brew/apt for missing tools
@@ -219,7 +255,7 @@ Supported tools: **Synthesis:** vivado, quartus, radiant, diamond, gowin.
219
255
 
220
256
  **Workflow:** `phase show/set/next/list` `ledger add/list` `req list/add/gaps/trace`
221
257
 
222
- **Agent:** `run` `agent providers/tools/skills`
258
+ **Agent:** `run` `agent run/plan/status/verify/improve/reports` `agent providers/tools/skills`
223
259
 
224
260
  **Ollama:** `ollama list/available/gpu/pull/suggest`
225
261
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "specsmith"
7
- version = "0.3.10.dev198"
7
+ version = "0.3.10.dev210"
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"
@@ -65,6 +65,8 @@ openai = ["openai>=1.0"]
65
65
  gemini = ["google-genai>=1.0"]
66
66
  mistral = ["openai>=1.0"] # Mistral uses the openai SDK pointed at api.mistral.ai
67
67
  gui = ["PySide6>=6.6"]
68
+ # AG2 agent shell (Planner/Builder/Verifier over Ollama)
69
+ ag2 = ["ag2[ollama]"]
68
70
  # Install all optional LLM providers
69
71
  agent = ["anthropic>=0.56", "openai>=1.0"]
70
72
  # Convenience bundle: everything
@@ -121,6 +123,8 @@ exclude = ["src/specsmith/gui"]
121
123
  module = [
122
124
  "anthropic",
123
125
  "anthropic.*",
126
+ "autogen", # AG2 agent shell — optional extra
127
+ "autogen.*",
124
128
  "openai",
125
129
  "openai.*",
126
130
  "google",
@@ -226,7 +226,8 @@ class OllamaProvider:
226
226
  data=json.dumps(payload).encode(),
227
227
  headers={"Content-Type": "application/json"},
228
228
  )
229
- with urllib.request.urlopen(req, timeout=120) as resp: # noqa: S310
229
+ # 300s: streaming responses arrive incrementally, but model load can take time
230
+ with urllib.request.urlopen(req, timeout=300) as resp: # noqa: S310
230
231
  for line in resp:
231
232
  line = line.strip()
232
233
  if not line:
@@ -294,7 +295,9 @@ class OllamaProvider:
294
295
  headers={"Content-Type": "application/json"},
295
296
  )
296
297
  try:
297
- with urllib.request.urlopen(req, timeout=120) as resp: # noqa: S310
298
+ # 600s timeout: Ollama needs time to load model (first call) + inference.
299
+ # 120s was too short and caused frequent "timed out" errors in VS Code.
300
+ with urllib.request.urlopen(req, timeout=600) as resp: # noqa: S310
298
301
  result: dict[str, Any] = json.loads(resp.read())
299
302
  return result
300
303
  except urllib.error.HTTPError as e:
@@ -224,6 +224,16 @@ If a command fails (non-zero exit, "not recognized", "not found"):
224
224
  "I couldn’t find [tool] on your PATH. Please tell me the full path or add it to PATH."
225
225
  5. NEVER loop between retrying and suggesting installation.
226
226
 
227
+ ## EXEC-001 — NO INLINE PYTHON EXECUTION:
228
+ NEVER run non-trivial Python code via `python -c "..."` or `python -c '...'`.
229
+ Always write Python code to a file first, then execute the file:
230
+ 1. Use write_file to create the script (e.g. /tmp/task.py or .specsmith/scripts/task.py)
231
+ 2. Use run_command to execute: `python task.py`
232
+ The ONLY exception: single-line import/version checks
233
+ (e.g. `python -c "import sys; print(sys.version)"`).
234
+ Reason: inline python -c cannot be interrupted, does not stream output,
235
+ and fails silently on Windows.
236
+
227
237
  ## TOOL CALL FORMAT RULE:
228
238
  NEVER output raw JSON tool calls as text. Use ONLY the native tool_use mechanism.
229
239
  Do NOT write <tools>, <tool_call>, or JSON blocks in your response text.
@@ -0,0 +1,7 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
3
+ """specsmith AG2 agent shell — Planner/Builder/Verifier over Ollama."""
4
+
5
+ from specsmith.agents.config import AgentConfig, load_agent_config
6
+
7
+ __all__ = ["AgentConfig", "load_agent_config"]
@@ -0,0 +1,233 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
3
+ """CLI commands for the AG2 agent shell.
4
+
5
+ Wired into the main specsmith CLI as the ``agent`` command group.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ import click
13
+ from rich.console import Console
14
+
15
+ console = Console()
16
+
17
+
18
+ @click.group()
19
+ def agent() -> None:
20
+ """AG2 agent shell — Planner/Builder/Verifier over Ollama."""
21
+
22
+
23
+ @agent.command()
24
+ @click.argument("task")
25
+ @click.option("--project-dir", default=".", help="Project root directory.")
26
+ @click.option("--max-turns", default=6, help="Maximum conversation turns per agent.")
27
+ def run(task: str, project_dir: str, max_turns: int) -> None:
28
+ """Execute a task through the full Planner → Builder → Verifier pipeline."""
29
+ try:
30
+ from autogen import ConversableAgent # noqa: F401 — verify AG2 is installed
31
+ except ImportError:
32
+ console.print("[red]AG2 is not installed.[/red] Run: pip install ag2[ollama]")
33
+ raise SystemExit(1) # noqa: B904
34
+
35
+ from specsmith.agents.config import load_agent_config
36
+ from specsmith.agents.roles import create_builder, create_planner, create_verifier
37
+
38
+ project_dir = str(Path(project_dir).resolve())
39
+ config = load_agent_config(project_dir)
40
+
41
+ console.print(
42
+ f"\n[bold cyan]specsmith agent shell[/bold cyan] — {config.primary_model} via Ollama"
43
+ )
44
+ console.print(f"Task: [bold]{task}[/bold]\n")
45
+
46
+ # Phase 1: Plan
47
+ console.print("[dim]─── Phase 1: Planning ───[/dim]")
48
+ planner = create_planner(config, project_dir)
49
+ plan_result = planner.run(message=f"Plan this task:\n{task}", max_turns=max_turns)
50
+ plan_result.process()
51
+
52
+ plan_text = ""
53
+ for msg in plan_result.messages:
54
+ if msg.get("role") == "assistant" and msg.get("content"):
55
+ plan_text = msg["content"]
56
+
57
+ if not plan_text:
58
+ console.print("[yellow]Planner produced no output.[/yellow]")
59
+ return
60
+
61
+ # Phase 2: Build
62
+ console.print("\n[dim]─── Phase 2: Building ───[/dim]")
63
+ builder = create_builder(config, project_dir)
64
+ build_result = builder.run(
65
+ message=f"Execute this plan:\n\n{plan_text}",
66
+ max_turns=max_turns,
67
+ )
68
+ build_result.process()
69
+
70
+ build_text = ""
71
+ for msg in build_result.messages:
72
+ if msg.get("role") == "assistant" and msg.get("content"):
73
+ build_text = msg["content"]
74
+
75
+ # Phase 3: Verify
76
+ console.print("\n[dim]─── Phase 3: Verifying ───[/dim]")
77
+ verifier = create_verifier(config, project_dir)
78
+ verify_result = verifier.run(
79
+ message=(
80
+ f"Verify the following changes:\n\n{build_text}"
81
+ "\n\nRun the relevant tests and report ACCEPT or REJECT."
82
+ ),
83
+ max_turns=max_turns,
84
+ )
85
+ verify_result.process()
86
+
87
+ # Summary
88
+ console.print("\n[bold cyan]─── Done ───[/bold cyan]")
89
+ for msg in verify_result.messages:
90
+ if msg.get("role") == "assistant" and msg.get("content"):
91
+ content = msg["content"]
92
+ if "ACCEPT" in content.upper():
93
+ console.print("[bold green]✓ ACCEPTED[/bold green]")
94
+ elif "REJECT" in content.upper():
95
+ console.print("[bold red]✗ REJECTED[/bold red]")
96
+ break
97
+
98
+
99
+ @agent.command()
100
+ @click.argument("task")
101
+ @click.option("--project-dir", default=".", help="Project root directory.")
102
+ @click.option("--max-turns", default=6, help="Maximum conversation turns.")
103
+ def plan(task: str, project_dir: str, max_turns: int) -> None:
104
+ """Generate a plan without executing it."""
105
+ try:
106
+ from autogen import ConversableAgent # noqa: F401
107
+ except ImportError:
108
+ console.print("[red]AG2 is not installed.[/red] Run: pip install ag2[ollama]")
109
+ raise SystemExit(1) # noqa: B904
110
+
111
+ from specsmith.agents.config import load_agent_config
112
+ from specsmith.agents.roles import create_planner
113
+
114
+ project_dir = str(Path(project_dir).resolve())
115
+ config = load_agent_config(project_dir)
116
+
117
+ console.print(f"\n[bold cyan]specsmith agent plan[/bold cyan] — {config.primary_model}")
118
+ planner = create_planner(config, project_dir)
119
+ result = planner.run(message=f"Plan this task:\n{task}", max_turns=max_turns)
120
+ result.process()
121
+
122
+
123
+ @agent.command()
124
+ @click.option("--project-dir", default=".", help="Project root directory.")
125
+ def status(project_dir: str) -> None:
126
+ """Show agent configuration and Ollama status."""
127
+ from specsmith.agents.config import load_agent_config
128
+
129
+ config = load_agent_config(project_dir)
130
+ console.print("[bold]Agent Configuration[/bold]")
131
+ console.print(f" Primary model: {config.primary_model}")
132
+ console.print(f" Utility model: {config.utility_model}")
133
+ console.print(f" Ollama URL: {config.ollama_base_url}")
134
+ console.print(f" Max iterations: {config.max_iterations}")
135
+ console.print(f" Context length: {config.num_ctx}")
136
+ console.print(f" Tools enabled: {', '.join(config.tools_enabled)}")
137
+
138
+ # Check Ollama
139
+ try:
140
+ from specsmith.agent.providers.ollama import OllamaProvider
141
+
142
+ p = OllamaProvider(base_url=config.ollama_base_url)
143
+ if p.is_available():
144
+ console.print(" Ollama: [green]running[/green]")
145
+ else:
146
+ console.print(" Ollama: [red]not available[/red]")
147
+ except Exception: # noqa: BLE001
148
+ console.print(" Ollama: [yellow]unknown[/yellow]")
149
+
150
+
151
+ @agent.command()
152
+ @click.option("--project-dir", default=".", help="Project root directory.")
153
+ @click.option("--max-turns", default=4, help="Maximum conversation turns.")
154
+ def verify(project_dir: str, max_turns: int) -> None:
155
+ """Run the Verifier agent on the current project state."""
156
+ try:
157
+ from autogen import ConversableAgent # noqa: F401
158
+ except ImportError:
159
+ console.print("[red]AG2 is not installed.[/red] Run: pip install ag2[ollama]")
160
+ raise SystemExit(1) # noqa: B904
161
+
162
+ from specsmith.agents.config import load_agent_config
163
+ from specsmith.agents.roles import create_verifier
164
+
165
+ project_dir = str(Path(project_dir).resolve())
166
+ config = load_agent_config(project_dir)
167
+
168
+ console.print(f"\n[bold cyan]specsmith agent verify[/bold cyan] — {config.utility_model}")
169
+ verifier = create_verifier(config, project_dir)
170
+ result = verifier.run(
171
+ message=(
172
+ "Run the full test suite and report the current project health."
173
+ " Report ACCEPT or REJECT."
174
+ ),
175
+ max_turns=max_turns,
176
+ )
177
+ result.process()
178
+
179
+
180
+ @agent.command()
181
+ @click.argument("task")
182
+ @click.option("--project-dir", default=".", help="Project root directory.")
183
+ @click.option("--max-turns", default=6, help="Maximum conversation turns.")
184
+ def improve(task: str, project_dir: str, max_turns: int) -> None:
185
+ """Run the self-improvement workflow (Plan → Build → Verify → Report)."""
186
+ try:
187
+ from autogen import ConversableAgent # noqa: F401
188
+ except ImportError:
189
+ console.print("[red]AG2 is not installed.[/red] Run: pip install ag2[ollama]")
190
+ raise SystemExit(1) # noqa: B904
191
+
192
+ from specsmith.agents.workflows.improve import run_improvement
193
+
194
+ project_dir = str(Path(project_dir).resolve())
195
+ console.print("\n[bold cyan]specsmith agent improve[/bold cyan]")
196
+ console.print(f"Task: [bold]{task}[/bold]\n")
197
+
198
+ report = run_improvement(task, project_dir, max_turns=max_turns)
199
+
200
+ console.print(f"\n[bold]Report:[/bold] {report.summary}")
201
+ if report.verdict == "ACCEPT":
202
+ console.print("[bold green]✓ ACCEPTED[/bold green]")
203
+ elif report.verdict == "REJECT":
204
+ console.print("[bold red]✗ REJECTED[/bold red]")
205
+ else:
206
+ console.print(f"[yellow]Verdict: {report.verdict}[/yellow]")
207
+
208
+ if report.follow_up_tasks:
209
+ console.print("\n[bold]Follow-up tasks:[/bold]")
210
+ for ft in report.follow_up_tasks:
211
+ console.print(f" - {ft}")
212
+
213
+ console.print(f"\n[dim]Report saved to .specsmith/agent-reports/{report.task_id}.json[/dim]")
214
+
215
+
216
+ @agent.command()
217
+ @click.option("--project-dir", default=".", help="Project root directory.")
218
+ def reports(project_dir: str) -> None:
219
+ """List recent improvement reports."""
220
+ from specsmith.agents.reports import list_reports
221
+
222
+ all_reports = list_reports(project_dir)
223
+ if not all_reports:
224
+ console.print("[yellow]No improvement reports found.[/yellow]")
225
+ return
226
+
227
+ for r in all_reports[:10]:
228
+ icon = {
229
+ "accepted": "[green]✓[/green]",
230
+ "rejected": "[red]✗[/red]",
231
+ "failed": "[red]![/red]",
232
+ }.get(r.status, "[yellow]?[/yellow]")
233
+ console.print(f" {icon} {r.task_id} — {r.summary}")
@@ -0,0 +1,74 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
3
+ """Agent configuration — loaded from scaffold.yml ``agents:`` key."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from dataclasses import dataclass, field
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+
12
+ @dataclass
13
+ class AgentConfig:
14
+ """Configuration for the AG2 agent shell."""
15
+
16
+ primary_model: str = "qwen2.5:14b"
17
+ utility_model: str = "" # empty = same as primary_model
18
+ ollama_base_url: str = "http://localhost:11434"
19
+ max_iterations: int = 10
20
+ stream: bool = False
21
+ num_ctx: int = 4096
22
+ tools_enabled: list[str] = field(
23
+ default_factory=lambda: ["filesystem", "shell", "git", "tests", "docs"]
24
+ )
25
+
26
+ @property
27
+ def effective_utility_model(self) -> str:
28
+ """Utility model, defaulting to primary if not set."""
29
+ return self.utility_model or self.primary_model
30
+
31
+ @property
32
+ def effective_max_iterations(self) -> int:
33
+ """0 means unlimited (use 999 as practical ceiling)."""
34
+ return self.max_iterations if self.max_iterations > 0 else 999
35
+
36
+ def llm_config_dict(self, model: str | None = None) -> dict[str, Any]:
37
+ """Return an AG2-compatible LLM config dict for Ollama."""
38
+ return {
39
+ "model": model or self.primary_model,
40
+ "api_type": "ollama",
41
+ "client_host": self.ollama_base_url,
42
+ "stream": self.stream,
43
+ "num_ctx": self.num_ctx,
44
+ "native_tool_calls": True,
45
+ "hide_tools": "if_any_run",
46
+ }
47
+
48
+
49
+ def load_agent_config(project_dir: str | Path) -> AgentConfig:
50
+ """Load agent config from scaffold.yml ``agents:`` section.
51
+
52
+ Falls back to defaults if scaffold.yml doesn't exist or has no agents key.
53
+ """
54
+ scaffold_path = Path(project_dir) / "scaffold.yml"
55
+ if not scaffold_path.exists():
56
+ return AgentConfig()
57
+
58
+ try:
59
+ import yaml
60
+
61
+ with open(scaffold_path, encoding="utf-8") as f:
62
+ raw = yaml.safe_load(f) or {}
63
+ agents_raw = raw.get("agents", {}) or {}
64
+ return AgentConfig(
65
+ primary_model=agents_raw.get("primary_model", AgentConfig.primary_model),
66
+ utility_model=agents_raw.get("utility_model", AgentConfig.utility_model),
67
+ ollama_base_url=agents_raw.get("ollama_base_url", AgentConfig.ollama_base_url),
68
+ max_iterations=agents_raw.get("max_iterations", AgentConfig.max_iterations),
69
+ stream=agents_raw.get("stream", AgentConfig.stream),
70
+ num_ctx=agents_raw.get("num_ctx", AgentConfig.num_ctx),
71
+ tools_enabled=agents_raw.get("tools_enabled", AgentConfig().tools_enabled),
72
+ )
73
+ except Exception: # noqa: BLE001
74
+ return AgentConfig()
@@ -0,0 +1,77 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
3
+ """Change reports — structured artifacts from improvement runs.
4
+
5
+ Every improvement run produces a ChangeReport stored as JSON at
6
+ ``.specsmith/agent-reports/<task_id>.json``.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ from dataclasses import asdict, dataclass, field
13
+ from datetime import datetime, timezone
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+
18
+ @dataclass
19
+ class ChangeReport:
20
+ """Structured output from an improvement run."""
21
+
22
+ task_id: str = ""
23
+ task_description: str = ""
24
+ project_dir: str = "."
25
+ status: str = "pending" # pending, accepted, rejected, failed, unclear
26
+ verdict: str = "" # ACCEPT, REJECT, UNCLEAR
27
+ plan: str = ""
28
+ build_output: str = ""
29
+ verify_output: str = ""
30
+ files_changed: list[str] = field(default_factory=list)
31
+ tests_run: int = 0
32
+ tests_passed: int = 0
33
+ tests_failed: int = 0
34
+ summary: str = ""
35
+ follow_up_tasks: list[str] = field(default_factory=list)
36
+ created: str = field(default_factory=lambda: datetime.now(tz=timezone.utc).isoformat())
37
+
38
+ def to_dict(self) -> dict[str, Any]:
39
+ """Convert to a JSON-serializable dict (excludes verbose fields)."""
40
+ d = asdict(self)
41
+ # Truncate verbose fields for the summary view
42
+ for key in ("plan", "build_output", "verify_output"):
43
+ if len(d.get(key, "")) > 500:
44
+ d[key] = d[key][:500] + "...(truncated)"
45
+ return d
46
+
47
+
48
+ def save_report(report: ChangeReport) -> Path:
49
+ """Save a change report to ``.specsmith/agent-reports/``."""
50
+ root = Path(report.project_dir).resolve()
51
+ reports_dir = root / ".specsmith" / "agent-reports"
52
+ reports_dir.mkdir(parents=True, exist_ok=True)
53
+ path = reports_dir / f"{report.task_id}.json"
54
+ path.write_text(
55
+ json.dumps(report.to_dict(), indent=2, ensure_ascii=False),
56
+ encoding="utf-8",
57
+ )
58
+ return path
59
+
60
+
61
+ def list_reports(project_dir: str = ".") -> list[ChangeReport]:
62
+ """List all change reports, newest first."""
63
+ reports_dir = Path(project_dir).resolve() / ".specsmith" / "agent-reports"
64
+ if not reports_dir.exists():
65
+ return []
66
+ reports: list[ChangeReport] = []
67
+ for path in sorted(reports_dir.glob("*.json"), reverse=True):
68
+ try:
69
+ data = json.loads(path.read_text(encoding="utf-8"))
70
+ reports.append(
71
+ ChangeReport(
72
+ **{k: v for k, v in data.items() if k in ChangeReport.__dataclass_fields__}
73
+ )
74
+ )
75
+ except Exception: # noqa: BLE001
76
+ pass
77
+ return reports