specsmith 0.15.3.dev570__tar.gz → 0.15.3.dev573__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 (303) hide show
  1. {specsmith-0.15.3.dev570/src/specsmith.egg-info → specsmith-0.15.3.dev573}/PKG-INFO +2 -2
  2. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/pyproject.toml +2 -2
  3. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/auditor.py +11 -4
  4. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/cli.py +217 -44
  5. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/esdb/sqlite_store.py +16 -0
  6. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/sync.py +143 -0
  7. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/gitignore.j2 +7 -0
  8. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573/src/specsmith.egg-info}/PKG-INFO +2 -2
  9. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith.egg-info/SOURCES.txt +2 -0
  10. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith.egg-info/requires.txt +1 -1
  11. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_auditor.py +18 -1
  12. specsmith-0.15.3.dev573/tests/test_cli_regressions.py +88 -0
  13. specsmith-0.15.3.dev573/tests/test_esdb_enforcement.py +130 -0
  14. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_scaffolder.py +3 -0
  15. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/LICENSE +0 -0
  16. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/README.md +0 -0
  17. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/setup.cfg +0 -0
  18. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/__init__.py +0 -0
  19. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/belief.py +0 -0
  20. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/certainty.py +0 -0
  21. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/failure_graph.py +0 -0
  22. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/py.typed +0 -0
  23. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/recovery.py +0 -0
  24. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/session.py +0 -0
  25. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/stress_tester.py +0 -0
  26. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/epistemic/trace.py +0 -0
  27. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/__init__.py +0 -0
  28. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/__main__.py +0 -0
  29. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/__init__.py +0 -0
  30. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/broker.py +0 -0
  31. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/chat_runner.py +0 -0
  32. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/cleanup.py +0 -0
  33. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/context_seed.py +0 -0
  34. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/core.py +0 -0
  35. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/dispatch/__init__.py +0 -0
  36. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/dispatch/_status.py +0 -0
  37. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/dispatch/dag.py +0 -0
  38. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/dispatch/dispatcher.py +0 -0
  39. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/dispatch/events.py +0 -0
  40. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/dispatch/result.py +0 -0
  41. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/endpoints.py +0 -0
  42. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/events.py +0 -0
  43. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/execution_profiles.py +0 -0
  44. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/fallback.py +0 -0
  45. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/hf_leaderboard.py +0 -0
  46. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/hf_sync.py +0 -0
  47. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/indexer.py +0 -0
  48. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/llm_client.py +0 -0
  49. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/mcp.py +0 -0
  50. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/memory.py +0 -0
  51. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/model_intelligence.py +0 -0
  52. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/model_profiles.py +0 -0
  53. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/orchestrator.py +0 -0
  54. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/permissions.py +0 -0
  55. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/profiles.py +0 -0
  56. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/provider_registry.py +0 -0
  57. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/repl.py +0 -0
  58. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/router.py +0 -0
  59. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/rules.py +0 -0
  60. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/runner.py +0 -0
  61. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/safety.py +0 -0
  62. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/spawner.py +0 -0
  63. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/suggester.py +0 -0
  64. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/teams.py +0 -0
  65. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/tools.py +0 -0
  66. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/verifier.py +0 -0
  67. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/agent/voice.py +0 -0
  68. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/architect.py +0 -0
  69. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/auth.py +0 -0
  70. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/block_export.py +0 -0
  71. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/channel.py +0 -0
  72. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/ci_manager.py +0 -0
  73. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/commands/__init__.py +0 -0
  74. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/commands/intelligence.py +0 -0
  75. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/compliance/__init__.py +0 -0
  76. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/compliance/_compat.py +0 -0
  77. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/compliance/checker.py +0 -0
  78. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/compliance/evidence.py +0 -0
  79. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/compliance/regulations.py +0 -0
  80. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/compliance/reporter.py +0 -0
  81. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/compressor.py +0 -0
  82. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/config.py +0 -0
  83. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/console_utils.py +0 -0
  84. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/context_orchestrator.py +0 -0
  85. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/context_window.py +0 -0
  86. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/credit_analyzer.py +0 -0
  87. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/credits.py +0 -0
  88. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/__init__.py +0 -0
  89. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/base.py +0 -0
  90. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/citations.py +0 -0
  91. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/fpd.py +0 -0
  92. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/odp.py +0 -0
  93. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/patentsview.py +0 -0
  94. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/pfw.py +0 -0
  95. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/ppubs.py +0 -0
  96. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/datasources/ptab.py +0 -0
  97. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/differ.py +0 -0
  98. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/doctor.py +0 -0
  99. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/drive.py +0 -0
  100. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/editor.py +0 -0
  101. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/epistemic/__init__.py +0 -0
  102. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/epistemic/belief.py +0 -0
  103. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/epistemic/certainty.py +0 -0
  104. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/epistemic/failure_graph.py +0 -0
  105. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/epistemic/recovery.py +0 -0
  106. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/epistemic/stress_tester.py +0 -0
  107. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/esdb/__init__.py +0 -0
  108. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/esdb/_license.py +0 -0
  109. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/esdb/bridge.py +0 -0
  110. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/eval/__init__.py +0 -0
  111. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/eval/builtins.py +0 -0
  112. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/eval/runner.py +0 -0
  113. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/executor.py +0 -0
  114. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/exporter.py +0 -0
  115. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/governance_logic.py +0 -0
  116. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/governance_store.py +0 -0
  117. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/governance_yaml.py +0 -0
  118. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/__init__.py +0 -0
  119. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/app.py +0 -0
  120. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/main_window.py +0 -0
  121. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/session_tab.py +0 -0
  122. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/theme.py +0 -0
  123. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/widgets/__init__.py +0 -0
  124. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/widgets/chat_view.py +0 -0
  125. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/widgets/input_bar.py +0 -0
  126. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/widgets/provider_bar.py +0 -0
  127. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/widgets/token_meter.py +0 -0
  128. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/widgets/tool_panel.py +0 -0
  129. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/widgets/update_checker.py +0 -0
  130. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/gui/worker.py +0 -0
  131. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/history_search.py +0 -0
  132. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/importer.py +0 -0
  133. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/instinct.py +0 -0
  134. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/__init__.py +0 -0
  135. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/agent_skill.py +0 -0
  136. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/aider.py +0 -0
  137. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/base.py +0 -0
  138. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/claude_code.py +0 -0
  139. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/codity.py +0 -0
  140. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/copilot.py +0 -0
  141. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/cursor.py +0 -0
  142. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/gemini.py +0 -0
  143. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/integrations/windsurf.py +0 -0
  144. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/issue_reporter.py +0 -0
  145. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/languages.py +0 -0
  146. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/ledger.py +0 -0
  147. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/mcp_generator.py +0 -0
  148. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/mcp_server.py +0 -0
  149. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/__init__.py +0 -0
  150. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/m001_governance_yaml.py +0 -0
  151. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/m002_agents_slim.py +0 -0
  152. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/m003_compliance_init.py +0 -0
  153. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/m004_ledger_esdb.py +0 -0
  154. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/m005_agent_run_tool.py +0 -0
  155. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/m006_session_governance.py +0 -0
  156. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/migrations/runner.py +0 -0
  157. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/ollama_cmds.py +0 -0
  158. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/patent.py +0 -0
  159. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/paths.py +0 -0
  160. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/phase.py +0 -0
  161. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/plugins.py +0 -0
  162. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/profiles.py +0 -0
  163. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/rate_limits.py +0 -0
  164. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/releaser.py +0 -0
  165. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/requirements.py +0 -0
  166. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/requirements_parser.py +0 -0
  167. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/retrieval.py +0 -0
  168. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/safe_write.py +0 -0
  169. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/scaffolder.py +0 -0
  170. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/serve.py +0 -0
  171. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/session.py +0 -0
  172. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/session_init.py +0 -0
  173. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/session_store.py +0 -0
  174. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/__init__.py +0 -0
  175. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/ai_agents.py +0 -0
  176. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/cloud.py +0 -0
  177. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/corporate.py +0 -0
  178. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/cross_platform.py +0 -0
  179. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/data_engineering.py +0 -0
  180. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/devops.py +0 -0
  181. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/docs.py +0 -0
  182. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/embedded.py +0 -0
  183. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/governance.py +0 -0
  184. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/hardware.py +0 -0
  185. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/mobile.py +0 -0
  186. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/platform_engineering.py +0 -0
  187. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/productivity.py +0 -0
  188. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/software_engineering.py +0 -0
  189. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/specsmith_skills.py +0 -0
  190. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/ssh.py +0 -0
  191. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills/web_backend.py +0 -0
  192. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/skills_builder.py +0 -0
  193. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/agents.md.j2 +0 -0
  194. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/bug_report.md.j2 +0 -0
  195. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/code_of_conduct.md.j2 +0 -0
  196. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/contributing.md.j2 +0 -0
  197. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/feature_request.md.j2 +0 -0
  198. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/license-Apache-2.0.j2 +0 -0
  199. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/license-MIT.j2 +0 -0
  200. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/pull_request_template.md.j2 +0 -0
  201. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/community/security.md.j2 +0 -0
  202. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
  203. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/docs/mkdocs.yml.j2 +0 -0
  204. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/docs/readthedocs.yaml.j2 +0 -0
  205. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
  206. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
  207. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/editorconfig.j2 +0 -0
  208. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/gitattributes.j2 +0 -0
  209. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/go/go.mod.j2 +0 -0
  210. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/go/main.go.j2 +0 -0
  211. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/belief-registry.md.j2 +0 -0
  212. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
  213. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
  214. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/epistemic-axioms.md.j2 +0 -0
  215. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/failure-modes.md.j2 +0 -0
  216. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/lifecycle.md.j2 +0 -0
  217. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/roles.md.j2 +0 -0
  218. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/rules.md.j2 +0 -0
  219. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/session-protocol.md.j2 +0 -0
  220. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/uncertainty-map.md.j2 +0 -0
  221. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/governance/verification.md.j2 +0 -0
  222. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/js/package.json.j2 +0 -0
  223. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/ledger.md.j2 +0 -0
  224. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/python/cli.py.j2 +0 -0
  225. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/python/init.py.j2 +0 -0
  226. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/python/pyproject.toml.j2 +0 -0
  227. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/readme.md.j2 +0 -0
  228. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/rust/Cargo.toml.j2 +0 -0
  229. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/rust/main.rs.j2 +0 -0
  230. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
  231. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
  232. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
  233. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
  234. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
  235. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
  236. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/templates/workflows/release.yml.j2 +0 -0
  237. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/tool_installer.py +0 -0
  238. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/toolrules.py +0 -0
  239. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/tools.py +0 -0
  240. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/trace.py +0 -0
  241. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/updater.py +0 -0
  242. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/upgrader.py +0 -0
  243. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/validator.py +0 -0
  244. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/vcs/__init__.py +0 -0
  245. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/vcs/base.py +0 -0
  246. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/vcs/bitbucket.py +0 -0
  247. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/vcs/github.py +0 -0
  248. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/vcs/gitlab.py +0 -0
  249. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/vcs_commands.py +0 -0
  250. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/wi_store.py +0 -0
  251. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/wireframes.py +0 -0
  252. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith/workspace.py +0 -0
  253. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith.egg-info/dependency_links.txt +0 -0
  254. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith.egg-info/entry_points.txt +0 -0
  255. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/src/specsmith.egg-info/top_level.txt +0 -0
  256. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_CMD_001.py +0 -0
  257. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_agent_profiles.py +0 -0
  258. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_agent_run_feedback.py +0 -0
  259. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_agent_runner_ready.py +0 -0
  260. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_ai_client.py +0 -0
  261. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_ai_intelligence.py +0 -0
  262. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_channel.py +0 -0
  263. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_chat_diff_decision.py +0 -0
  264. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_chat_runner_openai_compat.py +0 -0
  265. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_chat_stdin_protocol.py +0 -0
  266. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_cli.py +0 -0
  267. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_cli_workflows_history_drive.py +0 -0
  268. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_compliance.py +0 -0
  269. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_compliance_governance.py +0 -0
  270. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_compressor.py +0 -0
  271. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_dispatch.py +0 -0
  272. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_e2e_nexus.py +0 -0
  273. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_endpoints_cli.py +0 -0
  274. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_endpoints_store.py +0 -0
  275. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_epistemic.py +0 -0
  276. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_esdb_license.py +0 -0
  277. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_esdb_sqlite.py +0 -0
  278. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_fallback_chain.py +0 -0
  279. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_importer.py +0 -0
  280. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_integrations.py +0 -0
  281. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_integrations_codity.py +0 -0
  282. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_intelligence.py +0 -0
  283. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_issue_reporter.py +0 -0
  284. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_mcp_client.py +0 -0
  285. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_mcp_server.py +0 -0
  286. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_new_modules.py +0 -0
  287. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_nexus.py +0 -0
  288. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_permissions.py +0 -0
  289. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_phase1_4_new.py +0 -0
  290. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_phase34_completion.py +0 -0
  291. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_rate_limits.py +0 -0
  292. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_req_248_262.py +0 -0
  293. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_req_358_359.py +0 -0
  294. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_skill_marketplace.py +0 -0
  295. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_skills_mcp.py +0 -0
  296. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_smoke.py +0 -0
  297. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_suggester.py +0 -0
  298. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_tools.py +0 -0
  299. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_validator.py +0 -0
  300. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_vcs.py +0 -0
  301. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_warp_parity.py +0 -0
  302. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_warp_parity_followup.py +0 -0
  303. {specsmith-0.15.3.dev570 → specsmith-0.15.3.dev573}/tests/test_wi_lifecycle.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specsmith
3
- Version: 0.15.3.dev570
3
+ Version: 0.15.3.dev573
4
4
  Summary: AEE governance toolkit for AI-assisted development — session preflight gates, multi-agent dispatch, requirements↔test traceability, ESDB persistence, MCP server, and skills for Warp, Cursor, Claude Code, Copilot, Windsurf, and Aider.
5
5
  Author: Layer1Labs Silicon, Inc.
6
6
  License-Expression: MIT
@@ -40,7 +40,7 @@ Requires-Dist: pre-commit>=3.0; extra == "dev"
40
40
  Requires-Dist: types-pyyaml>=6.0; extra == "dev"
41
41
  Requires-Dist: cryptography>=42.0; extra == "dev"
42
42
  Provides-Extra: esdb
43
- Requires-Dist: chronomemory>=0.1.7; extra == "esdb"
43
+ Requires-Dist: chronomemory>=0.1.8; extra == "esdb"
44
44
  Requires-Dist: cryptography>=42.0; extra == "esdb"
45
45
  Provides-Extra: docs
46
46
  Requires-Dist: mkdocs>=1.6; extra == "docs"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "specsmith"
7
- version = "0.15.3.dev570"
7
+ version = "0.15.3.dev573"
8
8
  description = "AEE governance toolkit for AI-assisted development — session preflight gates, multi-agent dispatch, requirements↔test traceability, ESDB persistence, MCP server, and skills for Warp, Cursor, Claude Code, Copilot, Windsurf, and Aider."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -60,7 +60,7 @@ dev = [
60
60
  # Install: pip install specsmith[esdb]
61
61
  # Activate: specsmith esdb enable --key-file /path/to/your.esdb.key
62
62
  esdb = [
63
- "chronomemory>=0.1.7", # proprietary commercial — see chronomemory LICENSE
63
+ "chronomemory>=0.1.8", # proprietary commercial — see chronomemory LICENSE
64
64
  "cryptography>=42.0",
65
65
  ]
66
66
  docs = [
@@ -1130,7 +1130,11 @@ def check_industrial_artifacts(root: Path) -> list[AuditResult]:
1130
1130
  raw = _read_scaffold_raw(root)
1131
1131
  declared: dict[str, Any] = raw.get("industrial_artifacts", {}) or {}
1132
1132
  declared_eds = declared.get("canopen_eds", []) or []
1133
- declared_paths = {e.get("path", "") for e in declared_eds if isinstance(e, dict)}
1133
+ # Normalise declared paths to forward slashes and lowercase so they match
1134
+ # on Windows where YAML uses '/' but Path.relative_to() returns '\\' (#251).
1135
+ declared_paths = {
1136
+ e.get("path", "").replace("\\", "/").lower() for e in declared_eds if isinstance(e, dict)
1137
+ }
1134
1138
 
1135
1139
  # Scan for .eds and .xdd files — respect scan_exclude_dirs and
1136
1140
  # scan_exclude_patterns from scaffold.yml (#175).
@@ -1158,9 +1162,12 @@ def check_industrial_artifacts(root: Path) -> list[AuditResult]:
1158
1162
  if not found_eds:
1159
1163
  return results # No industrial artifacts found
1160
1164
 
1161
- # Use as_posix() for cross-platform comparison — avoids single-backslash
1162
- # mismatch on Windows where str(Path) uses '\' but declared paths use '/' (#173).
1163
- undeclared = [f for f in found_eds if f.relative_to(root).as_posix() not in declared_paths]
1165
+ # Use as_posix().lower() for cross-platform comparison — avoids backslash
1166
+ # mismatch on Windows where str(Path) uses '\' but YAML paths use '/' (#173,
1167
+ # #251). Both sides are lowercased so Windows case-insensitive FS is handled.
1168
+ undeclared = [
1169
+ f for f in found_eds if f.relative_to(root).as_posix().lower() not in declared_paths
1170
+ ]
1164
1171
 
1165
1172
  if undeclared:
1166
1173
  results.append(
@@ -445,8 +445,17 @@ def _interactive_config(no_git: bool) -> ProjectConfig:
445
445
  def audit(fix: bool, project_dir: str) -> None:
446
446
  """Run drift and health checks (Section 23 + 26)."""
447
447
  from specsmith.auditor import run_audit
448
+ from specsmith.sync import auto_migrate_if_needed
448
449
 
449
450
  root = Path(project_dir).resolve()
451
+ auto_counts = auto_migrate_if_needed(root)
452
+ if auto_counts:
453
+ console.print(
454
+ " [cyan]⟳[/cyan] ESDB auto-migrate: "
455
+ f"{auto_counts.get('requirements', 0)} requirements + "
456
+ f"{auto_counts.get('testcases', 0)} testcases "
457
+ f"({auto_counts.get('skipped', 0)} skipped)"
458
+ )
450
459
  console.print(f"[bold]Auditing[/bold] {root}\n")
451
460
  report = run_audit(root)
452
461
 
@@ -1489,14 +1498,20 @@ def diff(project_dir: str, html_output: str | None) -> None:
1489
1498
  default=False,
1490
1499
  help="Run the new-user onboarding checklist (REQ-127).",
1491
1500
  )
1492
- def doctor(project_dir: str, onboarding: bool) -> None:
1501
+ @click.option(
1502
+ "--json",
1503
+ "as_json",
1504
+ is_flag=True,
1505
+ default=False,
1506
+ help="Emit machine-readable health report JSON.",
1507
+ )
1508
+ def doctor(project_dir: str, onboarding: bool, as_json: bool) -> None:
1493
1509
  """Check if verification tools are installed locally.
1494
1510
 
1495
1511
  With --onboarding, walks through a 6-step checklist for new users that
1496
1512
  confirms scaffold.yml, governance files, agent provider setup, optional
1497
1513
  Nexus broker availability, and prints next-step commands (REQ-127).
1498
1514
  """
1499
- from specsmith.doctor import run_doctor
1500
1515
 
1501
1516
  root = Path(project_dir).resolve()
1502
1517
 
@@ -1572,28 +1587,108 @@ def doctor(project_dir: str, onboarding: bool) -> None:
1572
1587
  )
1573
1588
  return
1574
1589
 
1575
- report = run_doctor(root)
1590
+ import importlib.metadata
1591
+ import json as _json
1592
+ import shutil
1593
+ import subprocess
1594
+ import sys
1576
1595
 
1577
- if not report.checks:
1578
- console.print("[yellow]No scaffold.yml found cannot determine tools.[/yellow]")
1579
- return
1596
+ from specsmith.auditor import run_audit
1597
+ from specsmith.esdb import ESDB_BACKEND, open_default_store
1598
+ from specsmith.phase import read_phase
1580
1599
 
1581
- console.print(f"[bold]Doctor[/bold] checking {len(report.checks)} tools\n")
1582
- for check in report.checks:
1583
- if check.installed:
1584
- ver = f" ({check.version})" if check.version else ""
1585
- console.print(f" [green]✓[/green] {check.category}: {check.name}{ver}")
1586
- else:
1587
- console.print(f" [red]✗[/red] {check.category}: {check.name} — not found")
1600
+ def _tool_version(cmd: str) -> tuple[bool, str]:
1601
+ exe = shutil.which(cmd)
1602
+ if not exe:
1603
+ return False, "not found"
1604
+ try:
1605
+ proc = subprocess.run(
1606
+ [exe, "--version"],
1607
+ capture_output=True,
1608
+ text=True,
1609
+ timeout=5,
1610
+ check=False,
1611
+ )
1612
+ out = (proc.stdout or proc.stderr or "").strip().splitlines()
1613
+ if out:
1614
+ return True, out[0]
1615
+ return True, "installed"
1616
+ except (OSError, subprocess.TimeoutExpired):
1617
+ return True, "installed"
1618
+
1619
+ checks: list[dict[str, str]] = []
1620
+
1621
+ def _add(name: str, passed: bool, detail: str, warn: bool = False) -> None:
1622
+ status = "warn" if warn else ("pass" if passed else "fail")
1623
+ checks.append({"name": name, "status": status, "detail": detail})
1624
+
1625
+ py_ok = sys.version_info >= (3, 10)
1626
+ _add(
1627
+ "python",
1628
+ py_ok,
1629
+ f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
1630
+ )
1588
1631
 
1589
- console.print()
1590
- if report.missing_count == 0:
1591
- console.print(f"[bold green]All {report.installed_count} tools available.[/bold green]")
1592
- else:
1593
- console.print(
1594
- f"[bold red]{report.missing_count} tool(s) missing.[/bold red] "
1595
- f"{report.installed_count} installed."
1632
+ git_ok, git_detail = _tool_version("git")
1633
+ _add("git", git_ok, git_detail)
1634
+
1635
+ _add("specsmith", True, __version__)
1636
+
1637
+ try:
1638
+ chrono_ver = importlib.metadata.version("chronomemory")
1639
+ _add("chronomemory", True, chrono_ver)
1640
+ except importlib.metadata.PackageNotFoundError:
1641
+ _add("chronomemory", False, "not installed")
1642
+
1643
+ esdb_open = False
1644
+ chain_ok = False
1645
+ record_count = 0
1646
+ try:
1647
+ with open_default_store(root, warn=False) as store: # type: ignore[attr-defined]
1648
+ esdb_open = True
1649
+ record_count = int(store.record_count())
1650
+ chain_ok = store.chain_valid() is not False
1651
+ except Exception as exc: # noqa: BLE001
1652
+ _add("esdb backend", False, f"open failed: {exc}")
1653
+ if esdb_open:
1654
+ _add("esdb backend", True, f"{ESDB_BACKEND} open")
1655
+ _add(
1656
+ "esdb chain",
1657
+ chain_ok,
1658
+ f"{'valid' if chain_ok else 'invalid'} ({record_count} records)",
1596
1659
  )
1660
+ _add("esdb record count", True, str(record_count))
1661
+
1662
+ audit_report = run_audit(root)
1663
+ _add(
1664
+ "audit",
1665
+ audit_report.healthy,
1666
+ "clean" if audit_report.healthy else f"{audit_report.failed} issue(s)",
1667
+ )
1668
+
1669
+ phase_key = read_phase(root)
1670
+ _add("phase", True, phase_key)
1671
+
1672
+ for tool in ("ruff", "pytest", "pipx"):
1673
+ ok, detail = _tool_version(tool)
1674
+ _add(tool, ok, detail)
1675
+
1676
+ overall = "pass" if all(c["status"] != "fail" for c in checks) else "fail"
1677
+
1678
+ if as_json:
1679
+ click.echo(_json.dumps({"checks": checks, "overall": overall}, indent=2))
1680
+ return
1681
+
1682
+ icon = {"pass": "✅", "fail": "❌", "warn": "⚠️"}
1683
+ console.print("specsmith doctor")
1684
+ console.print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
1685
+ for c in checks:
1686
+ console.print(f" {c['name']:<14} {icon[c['status']]} {c['detail']}")
1687
+ console.print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
1688
+ if overall == "pass":
1689
+ console.print("All checks passed.")
1690
+ else:
1691
+ console.print("One or more checks failed.")
1597
1692
 
1598
1693
 
1599
1694
  @main.command()
@@ -2577,30 +2672,47 @@ def save_cmd(project_dir: str, message: str, no_push: bool, force: bool, as_json
2577
2672
  """
2578
2673
  import json as _json
2579
2674
 
2580
- from specsmith.esdb.bridge import EsdbBridge
2675
+ from specsmith import esdb as esdb_mod
2676
+ from specsmith.sync import normalize_esdb_gitignore_policy
2581
2677
  from specsmith.vcs_commands import has_uncommitted_changes, run_commit, run_push
2582
2678
 
2583
2679
  root = Path(project_dir).resolve()
2584
2680
  steps: list[dict[str, Any]] = []
2681
+ # 0. Normalize ESDB policy for legacy projects
2682
+ try:
2683
+ changed = normalize_esdb_gitignore_policy(root)
2684
+ steps.append(
2685
+ {
2686
+ "step": "gitignore_policy",
2687
+ "ok": True,
2688
+ "note": "normalized legacy ESDB policy" if changed else "already compliant",
2689
+ }
2690
+ )
2691
+ except Exception as exc: # noqa: BLE001
2692
+ steps.append({"step": "gitignore_policy", "ok": False, "error": str(exc)})
2585
2693
 
2586
2694
  # 1. ESDB backup
2587
2695
  try:
2588
- bridge = EsdbBridge(str(root))
2589
- store = bridge._get_store() # noqa: SLF001
2590
- if store is not None:
2591
- backup_path = store.backup()
2592
- steps.append({"step": "esdb_backup", "ok": True, "path": str(backup_path)})
2593
- else:
2594
- steps.append(
2595
- {"step": "esdb_backup", "ok": True, "note": "JSON fallback (no WAL to backup)"}
2596
- )
2597
- except ImportError:
2598
- # chronomemory not installed — non-fatal; commit and push still proceed.
2599
- # Install with: pipx inject specsmith
2600
- # "chronomemory @ git+https://github.com/layer1labs/chronomemory.git@v0.1.1"
2601
- from specsmith.esdb import _INSTALL_HINT # noqa: PLC0415
2602
-
2603
- steps.append({"step": "esdb_backup", "ok": True, "note": f"skipped — {_INSTALL_HINT}"})
2696
+ with esdb_mod.open_default_store(root, warn=False) as store: # type: ignore[attr-defined]
2697
+ backup_fn = getattr(store, "backup", None)
2698
+ if callable(backup_fn):
2699
+ backup_path = backup_fn()
2700
+ steps.append(
2701
+ {
2702
+ "step": "esdb_backup",
2703
+ "ok": True,
2704
+ "path": str(backup_path),
2705
+ "backend": esdb_mod.ESDB_BACKEND,
2706
+ }
2707
+ )
2708
+ else:
2709
+ steps.append(
2710
+ {
2711
+ "step": "esdb_backup",
2712
+ "ok": True,
2713
+ "note": f"{esdb_mod.ESDB_BACKEND} backend has no native backup method",
2714
+ }
2715
+ )
2604
2716
  except Exception as exc: # noqa: BLE001
2605
2717
  steps.append({"step": "esdb_backup", "ok": False, "error": str(exc)})
2606
2718
 
@@ -5653,7 +5765,8 @@ def phase_next(project_dir: str, force: bool) -> None:
5653
5765
  """Advance to the next AEE workflow phase.
5654
5766
 
5655
5767
  Performs readiness checks on the current phase first.
5656
- Use --force to skip checks.
5768
+ Use --force to skip checks. Phases listed in ``suppressed_phases`` in the
5769
+ scaffold config (docs/SPECSMITH.yml) are automatically skipped (#254).
5657
5770
  """
5658
5771
  from specsmith.phase import PHASE_MAP, evaluate_phase, read_phase, write_phase
5659
5772
 
@@ -5679,12 +5792,45 @@ def phase_next(project_dir: str, force: bool) -> None:
5679
5792
  )
5680
5793
  return
5681
5794
 
5682
- write_phase(root, phase.next_phase)
5683
- next_phase = PHASE_MAP[phase.next_phase]
5795
+ # Read suppressed_phases from the scaffold config (#254).
5796
+ suppressed: list[str] = []
5797
+ try:
5798
+ import yaml as _yaml
5799
+
5800
+ from specsmith.paths import find_scaffold
5801
+
5802
+ sp = find_scaffold(root)
5803
+ if sp:
5804
+ _raw = _yaml.safe_load(sp.read_text(encoding="utf-8")) or {}
5805
+ suppressed = [str(p) for p in (_raw.get("suppressed_phases") or [])]
5806
+ except Exception: # noqa: BLE001
5807
+ pass # Never block phase advance on config parse errors
5808
+
5809
+ # Walk the phase chain until we find a non-suppressed next phase.
5810
+ target_key: str | None = phase.next_phase
5811
+ skipped: list[str] = []
5812
+ while target_key and target_key in suppressed:
5813
+ skipped.append(target_key)
5814
+ target_phase = PHASE_MAP.get(target_key)
5815
+ target_key = target_phase.next_phase if target_phase else None
5816
+
5817
+ if not target_key:
5818
+ console.print(
5819
+ f"[bold]{phase.emoji} {phase.label}[/bold] is the effective final phase "
5820
+ "(all remaining phases are suppressed)."
5821
+ )
5822
+ if skipped:
5823
+ console.print(f" [dim]Suppressed: {', '.join(skipped)}[/dim]")
5824
+ return
5825
+
5826
+ write_phase(root, target_key)
5827
+ next_phase = PHASE_MAP[target_key]
5684
5828
  console.print(
5685
5829
  f"[green]\u2713[/green] Advanced from [bold]{phase.label}[/bold] "
5686
5830
  f"to [bold]{next_phase.emoji} {next_phase.label}[/bold]."
5687
5831
  )
5832
+ if skipped:
5833
+ console.print(f" [dim]Skipped suppressed phase(s): {', '.join(skipped)}[/dim]")
5688
5834
  console.print(f" {next_phase.description}")
5689
5835
  if next_phase.commands:
5690
5836
  console.print("\n [bold]Next steps:[/bold]")
@@ -5693,14 +5839,14 @@ def phase_next(project_dir: str, force: bool) -> None:
5693
5839
 
5694
5840
  # G3: keep the agents routing table aligned with the active phase.
5695
5841
  # We pin a synthetic ``phase:active`` route so the runner can flip the
5696
- # whole session to the new phases preferred profile without the user
5842
+ # whole session to the new phase's preferred profile without the user
5697
5843
  # having to run `specsmith agents route set` themselves.
5698
5844
  try:
5699
5845
  from specsmith.agent.profiles import ProfileStore
5700
5846
 
5701
5847
  agents_store = ProfileStore.load()
5702
5848
  if agents_store.profiles:
5703
- phase_key_target = f"phase:{phase.next_phase}"
5849
+ phase_key_target = f"phase:{target_key}"
5704
5850
  target_id = agents_store.routes.get(phase_key_target) or (
5705
5851
  agents_store.default_profile_id
5706
5852
  )
@@ -5968,10 +6114,11 @@ def sync_cmd(project_dir: str, check_only: bool, as_json: bool) -> None:
5968
6114
  """
5969
6115
  import json as _json
5970
6116
 
5971
- from specsmith.sync import run_sync
6117
+ from specsmith.sync import auto_migrate_if_needed, run_sync
5972
6118
 
5973
6119
  root = Path(project_dir).resolve()
5974
6120
  result = run_sync(root, dry_run=check_only)
6121
+ auto_counts = {} if check_only else auto_migrate_if_needed(root)
5975
6122
 
5976
6123
  if as_json:
5977
6124
  click.echo(
@@ -5985,6 +6132,8 @@ def sync_cmd(project_dir: str, check_only: bool, as_json: bool) -> None:
5985
6132
  "tests_changed": result.tests_changed,
5986
6133
  "in_sync": not result.changed,
5987
6134
  "dry_run": result.dry_run,
6135
+ "auto_migrated": bool(auto_counts),
6136
+ "auto_migrate_counts": auto_counts,
5988
6137
  },
5989
6138
  indent=2,
5990
6139
  )
@@ -5998,6 +6147,13 @@ def sync_cmd(project_dir: str, check_only: bool, as_json: bool) -> None:
5998
6147
  console.print(f"[green]\u2713[/green] {result.message}")
5999
6148
  else:
6000
6149
  console.print("[green]\u2713[/green] Machine state already in sync.")
6150
+ if auto_counts:
6151
+ console.print(
6152
+ " [cyan]⟳[/cyan] ESDB auto-migrate: "
6153
+ f"{auto_counts.get('requirements', 0)} requirements + "
6154
+ f"{auto_counts.get('testcases', 0)} testcases "
6155
+ f"({auto_counts.get('skipped', 0)} skipped)"
6156
+ )
6001
6157
 
6002
6158
  if check_only and result.changed:
6003
6159
  raise SystemExit(1)
@@ -10424,8 +10580,10 @@ def esdb_status_cmd(project_dir: str, as_json: bool) -> None:
10424
10580
 
10425
10581
  from specsmith.esdb import CHRONO_AVAILABLE, open_default_store
10426
10582
  from specsmith.esdb._license import check_license, resolve_license_path
10583
+ from specsmith.sync import auto_migrate_if_needed
10427
10584
 
10428
10585
  root = Path(project_dir).resolve()
10586
+ auto_counts = auto_migrate_if_needed(root)
10429
10587
 
10430
10588
  # Determine license status without side-effect warnings
10431
10589
  lic_path = resolve_license_path()
@@ -10491,6 +10649,8 @@ def esdb_status_cmd(project_dir: str, as_json: bool) -> None:
10491
10649
  "chain_valid": chain_ok,
10492
10650
  "counts": counts,
10493
10651
  "license": license_info,
10652
+ "auto_migrated": bool(auto_counts),
10653
+ "auto_migrate_counts": auto_counts,
10494
10654
  },
10495
10655
  indent=2,
10496
10656
  )
@@ -10521,6 +10681,13 @@ def esdb_status_cmd(project_dir: str, as_json: bool) -> None:
10521
10681
  console.print(
10522
10682
  " [dim]Use 'specsmith esdb enable --key-file <path>' to activate ChronoStore.[/dim]"
10523
10683
  )
10684
+ if auto_counts:
10685
+ console.print(
10686
+ " [cyan]⟳[/cyan] Auto-migrated from legacy JSON: "
10687
+ f"{auto_counts.get('requirements', 0)} requirements + "
10688
+ f"{auto_counts.get('testcases', 0)} testcases "
10689
+ f"({auto_counts.get('skipped', 0)} skipped)"
10690
+ )
10524
10691
 
10525
10692
 
10526
10693
  @esdb_group.command(name="enable")
@@ -10554,9 +10721,15 @@ def esdb_enable_cmd(key_file: str, as_json: bool) -> None:
10554
10721
  console.print("[dim]Contact licensing@layer1labs.com to obtain a valid license.[/dim]")
10555
10722
  raise SystemExit(1)
10556
10723
 
10724
+ import os
10725
+
10557
10726
  dest = Path.home() / ".specsmith" / "esdb.key"
10558
10727
  dest.parent.mkdir(parents=True, exist_ok=True)
10559
- shutil.copy2(key_file, dest)
10728
+ # Skip the copy when source and destination are the same file (#249).
10729
+ # On Windows, shutil.copy2(src, dst) raises PermissionError / WinError 32
10730
+ # when src == dst (file is already at the expected location).
10731
+ if os.path.realpath(str(key_file)) != os.path.realpath(str(dest)):
10732
+ shutil.copy2(key_file, dest)
10560
10733
 
10561
10734
  if as_json:
10562
10735
  click.echo(
@@ -289,6 +289,22 @@ class SqliteStore:
289
289
  row = conn.execute("SELECT MAX(rowid) FROM records").fetchone()
290
290
  return int(row[0]) if row and row[0] is not None else 0
291
291
 
292
+ def backup(self, backup_dir: str | Path | None = None) -> Path:
293
+ """Create a timestamped SQLite snapshot and return its path."""
294
+ conn = self._require_open()
295
+ ts = time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
296
+ dest_dir = Path(backup_dir) if backup_dir else self._db_path.parent / "backups"
297
+ dest_dir.mkdir(parents=True, exist_ok=True)
298
+ dest = dest_dir / f"esdb_sqlite_backup_{ts}.sqlite3"
299
+
300
+ backup_conn = sqlite3.connect(str(dest))
301
+ try:
302
+ conn.backup(backup_conn)
303
+ finally:
304
+ backup_conn.close()
305
+
306
+ return dest
307
+
292
308
  def chain_valid(self) -> bool:
293
309
  """Always True — SQLite ACID guarantees integrity (no SHA-256 WAL chain)."""
294
310
  return True
@@ -207,6 +207,88 @@ class SyncResult:
207
207
  return "Machine state already in sync."
208
208
 
209
209
 
210
+ # ---------------------------------------------------------------------------
211
+ # ESDB legacy policy normalization (.gitignore)
212
+ # ---------------------------------------------------------------------------
213
+
214
+ _ESDB_GITIGNORE_FORBIDDEN = frozenset(
215
+ {
216
+ ".specsmith/",
217
+ ".chronomemory/",
218
+ }
219
+ )
220
+
221
+ _ESDB_GITIGNORE_REQUIRED = (
222
+ "!.specsmith/config.yml",
223
+ "!.specsmith/requirements.json",
224
+ "!.specsmith/testcases.json",
225
+ "!.specsmith/esdb.sqlite3",
226
+ "!.chronomemory/events.wal",
227
+ "!.chronomemory/snapshot.json",
228
+ ".chronomemory/backup/",
229
+ ".specsmith/workitems.json",
230
+ ".specsmith/runs/",
231
+ ".specsmith/chat/",
232
+ ".specsmith/perf/",
233
+ ".specsmith/recovery/",
234
+ ".specsmith/ledger.jsonl",
235
+ ".specsmith/ledger-chain.txt",
236
+ ".specsmith/trace.jsonl",
237
+ ".specsmith/credit-budget.json",
238
+ ".specsmith/credits.json",
239
+ ".specsmith/model-rate-limits.json",
240
+ ".specsmith/pids/",
241
+ ".specsmith/logs/",
242
+ ".specsmith/sessions/",
243
+ ".specsmith/agent-reports/",
244
+ ".specsmith/dispatch/",
245
+ ".specsmith/esdb_migration_manifest.json",
246
+ )
247
+
248
+
249
+ def normalize_esdb_gitignore_policy(root: Path, *, dry_run: bool = False) -> bool:
250
+ """Normalize ESDB gitignore policy for legacy projects.
251
+
252
+ Ensures broad ignores that hide canonical ESDB state are removed and that
253
+ explicit policy lines are present so SQLite/Chrono canonical artifacts are
254
+ always tracked while runtime files stay ignored.
255
+ """
256
+ gitignore_path = root / ".gitignore"
257
+ if gitignore_path.exists():
258
+ original_lines = gitignore_path.read_text(encoding="utf-8").splitlines()
259
+ else:
260
+ original_lines = []
261
+
262
+ normalized_lines: list[str] = []
263
+ removed_forbidden = False
264
+ for line in original_lines:
265
+ if line.strip() in _ESDB_GITIGNORE_FORBIDDEN:
266
+ removed_forbidden = True
267
+ continue
268
+ normalized_lines.append(line)
269
+
270
+ existing = {line.strip() for line in normalized_lines if line.strip()}
271
+ missing = [entry for entry in _ESDB_GITIGNORE_REQUIRED if entry not in existing]
272
+ changed = removed_forbidden or bool(missing)
273
+
274
+ if not changed:
275
+ return False
276
+
277
+ if normalized_lines and normalized_lines[-1].strip():
278
+ normalized_lines.append("")
279
+ if missing:
280
+ normalized_lines.append("# specsmith ESDB policy (auto-normalized)")
281
+ normalized_lines.extend(missing)
282
+
283
+ if not dry_run:
284
+ rendered = "\n".join(normalized_lines)
285
+ if rendered and not rendered.endswith("\n"):
286
+ rendered += "\n"
287
+ gitignore_path.write_text(rendered, encoding="utf-8")
288
+
289
+ return True
290
+
291
+
210
292
  # ---------------------------------------------------------------------------
211
293
  # Core sync
212
294
  # ---------------------------------------------------------------------------
@@ -244,6 +326,8 @@ def run_sync(root: Path, *, dry_run: bool = False) -> SyncResult:
244
326
  tests_md_path = root / "docs" / "TESTS.md"
245
327
  reqs_json_path = state_dir / "requirements.json"
246
328
  tests_json_path = state_dir / "testcases.json"
329
+ if not dry_run:
330
+ normalize_esdb_gitignore_policy(root)
247
331
 
248
332
  if is_yaml_mode(root):
249
333
  # ── YAML-first mode ─────────────────────────────────────────────────
@@ -469,6 +553,65 @@ def _sync_esdb(root: Path, state_dir: Path) -> None:
469
553
  pass
470
554
 
471
555
 
556
+ def _json_file_has_content(path: Path) -> bool:
557
+ """Return True when *path* exists and contains a non-empty JSON list.
558
+
559
+ Falls back to a small size heuristic for malformed JSON so we still treat
560
+ legacy state files with obvious content as migratable.
561
+ """
562
+ if not path.is_file():
563
+ return False
564
+ try:
565
+ payload = json.loads(path.read_text(encoding="utf-8"))
566
+ return isinstance(payload, list) and len(payload) > 0
567
+ except (OSError, ValueError):
568
+ with contextlib.suppress(OSError):
569
+ return path.stat().st_size > 2
570
+ return False
571
+
572
+
573
+ def _should_auto_migrate(store: Any, specsmith_dir: Path) -> bool:
574
+ """Return True when ESDB is empty and legacy JSON sources have content.
575
+
576
+ This is used by CLI surfaces (`sync`, `audit`, `esdb status`) to
577
+ opportunistically bootstrap ESDB from `.specsmith/*.json` exactly once.
578
+ """
579
+ if store.record_count() > 0:
580
+ return False
581
+ candidates = (
582
+ specsmith_dir / "requirements.json",
583
+ specsmith_dir / "testcases.json",
584
+ )
585
+ return any(_json_file_has_content(path) for path in candidates)
586
+
587
+
588
+ def auto_migrate_if_needed(root: Path) -> dict[str, int]:
589
+ """Best-effort ESDB bootstrap from legacy JSON when the store is empty.
590
+
591
+ Returns migration counts on success, or an empty dict when no migration was
592
+ needed or if any backend error occurred.
593
+ """
594
+ from specsmith.esdb import open_default_store
595
+
596
+ specsmith_dir = root / ".specsmith"
597
+ if not specsmith_dir.exists():
598
+ return {}
599
+ try:
600
+ with open_default_store(root, warn=False) as store: # type: ignore[attr-defined]
601
+ if not _should_auto_migrate(store, specsmith_dir):
602
+ return {}
603
+ counts = store.migrate_from_json(specsmith_dir)
604
+ if isinstance(counts, dict):
605
+ return {
606
+ "requirements": int(counts.get("requirements", 0)),
607
+ "testcases": int(counts.get("testcases", 0)),
608
+ "skipped": int(counts.get("skipped", 0)),
609
+ }
610
+ return {}
611
+ except Exception: # noqa: BLE001 - never block caller commands
612
+ return {}
613
+
614
+
472
615
  def check_sync(root: Path) -> SyncResult:
473
616
  """Check whether machine state is in sync without writing anything.
474
617
 
@@ -45,6 +45,13 @@ htmlcov/
45
45
  # - .specsmith/esdb.sqlite3
46
46
  # - .chronomemory/events.wal
47
47
  # - .chronomemory/snapshot.json
48
+ # Always track these even if broader ignore rules are added later:
49
+ !.specsmith/config.yml
50
+ !.specsmith/requirements.json
51
+ !.specsmith/testcases.json
52
+ !.specsmith/esdb.sqlite3
53
+ !.chronomemory/events.wal
54
+ !.chronomemory/snapshot.json
48
55
  #
49
56
  # Ignore runtime-only / regeneratable artifacts:
50
57
  .chronomemory/backup/