specsmith 0.5.0.dev224__tar.gz → 0.5.0.dev225__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 (170) hide show
  1. {specsmith-0.5.0.dev224/src/specsmith.egg-info → specsmith-0.5.0.dev225}/PKG-INFO +1 -1
  2. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/pyproject.toml +1 -1
  3. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/cli.py +409 -0
  4. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225/src/specsmith.egg-info}/PKG-INFO +1 -1
  5. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith.egg-info/SOURCES.txt +1 -0
  6. specsmith-0.5.0.dev225/tests/test_cli_workflows_history_drive.py +281 -0
  7. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/LICENSE +0 -0
  8. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/README.md +0 -0
  9. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/setup.cfg +0 -0
  10. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/__init__.py +0 -0
  11. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/belief.py +0 -0
  12. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/certainty.py +0 -0
  13. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/failure_graph.py +0 -0
  14. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/py.typed +0 -0
  15. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/recovery.py +0 -0
  16. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/session.py +0 -0
  17. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/stress_tester.py +0 -0
  18. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/epistemic/trace.py +0 -0
  19. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/__init__.py +0 -0
  20. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/__main__.py +0 -0
  21. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/__init__.py +0 -0
  22. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/broker.py +0 -0
  23. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/cleanup.py +0 -0
  24. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/events.py +0 -0
  25. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/indexer.py +0 -0
  26. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/mcp.py +0 -0
  27. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/memory.py +0 -0
  28. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/orchestrator.py +0 -0
  29. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/repl.py +0 -0
  30. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/router.py +0 -0
  31. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/rules.py +0 -0
  32. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/safety.py +0 -0
  33. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/tools.py +0 -0
  34. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/agent/verifier.py +0 -0
  35. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/architect.py +0 -0
  36. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/auditor.py +0 -0
  37. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/auth.py +0 -0
  38. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/commands/__init__.py +0 -0
  39. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/compressor.py +0 -0
  40. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/config.py +0 -0
  41. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/console_utils.py +0 -0
  42. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/credit_analyzer.py +0 -0
  43. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/credits.py +0 -0
  44. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/differ.py +0 -0
  45. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/doctor.py +0 -0
  46. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/epistemic/__init__.py +0 -0
  47. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/epistemic/belief.py +0 -0
  48. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/epistemic/certainty.py +0 -0
  49. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/epistemic/failure_graph.py +0 -0
  50. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/epistemic/recovery.py +0 -0
  51. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/epistemic/stress_tester.py +0 -0
  52. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/executor.py +0 -0
  53. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/exporter.py +0 -0
  54. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/__init__.py +0 -0
  55. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/app.py +0 -0
  56. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/main_window.py +0 -0
  57. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/session_tab.py +0 -0
  58. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/theme.py +0 -0
  59. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/widgets/__init__.py +0 -0
  60. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/widgets/chat_view.py +0 -0
  61. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/widgets/input_bar.py +0 -0
  62. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/widgets/provider_bar.py +0 -0
  63. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/widgets/token_meter.py +0 -0
  64. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/widgets/tool_panel.py +0 -0
  65. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/widgets/update_checker.py +0 -0
  66. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/gui/worker.py +0 -0
  67. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/importer.py +0 -0
  68. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/__init__.py +0 -0
  69. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/agent_skill.py +0 -0
  70. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/aider.py +0 -0
  71. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/base.py +0 -0
  72. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/claude_code.py +0 -0
  73. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/copilot.py +0 -0
  74. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/cursor.py +0 -0
  75. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/gemini.py +0 -0
  76. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/integrations/windsurf.py +0 -0
  77. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/languages.py +0 -0
  78. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/ledger.py +0 -0
  79. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/patent.py +0 -0
  80. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/phase.py +0 -0
  81. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/plugins.py +0 -0
  82. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/profiles.py +0 -0
  83. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/rate_limits.py +0 -0
  84. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/releaser.py +0 -0
  85. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/requirements.py +0 -0
  86. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/requirements_parser.py +0 -0
  87. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/retrieval.py +0 -0
  88. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/scaffolder.py +0 -0
  89. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/serve.py +0 -0
  90. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/session.py +0 -0
  91. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/agents.md.j2 +0 -0
  92. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/bug_report.md.j2 +0 -0
  93. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/code_of_conduct.md.j2 +0 -0
  94. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/contributing.md.j2 +0 -0
  95. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/feature_request.md.j2 +0 -0
  96. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/license-Apache-2.0.j2 +0 -0
  97. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/license-MIT.j2 +0 -0
  98. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/pull_request_template.md.j2 +0 -0
  99. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/community/security.md.j2 +0 -0
  100. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
  101. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/docs/mkdocs.yml.j2 +0 -0
  102. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/docs/readthedocs.yaml.j2 +0 -0
  103. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
  104. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
  105. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/editorconfig.j2 +0 -0
  106. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/gitattributes.j2 +0 -0
  107. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/gitignore.j2 +0 -0
  108. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/go/go.mod.j2 +0 -0
  109. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/go/main.go.j2 +0 -0
  110. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/belief-registry.md.j2 +0 -0
  111. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
  112. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
  113. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/epistemic-axioms.md.j2 +0 -0
  114. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/failure-modes.md.j2 +0 -0
  115. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/lifecycle.md.j2 +0 -0
  116. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/roles.md.j2 +0 -0
  117. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/rules.md.j2 +0 -0
  118. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/session-protocol.md.j2 +0 -0
  119. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/uncertainty-map.md.j2 +0 -0
  120. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/governance/verification.md.j2 +0 -0
  121. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/js/package.json.j2 +0 -0
  122. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/ledger.md.j2 +0 -0
  123. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/python/cli.py.j2 +0 -0
  124. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/python/init.py.j2 +0 -0
  125. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/python/pyproject.toml.j2 +0 -0
  126. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/readme.md.j2 +0 -0
  127. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/rust/Cargo.toml.j2 +0 -0
  128. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/rust/main.rs.j2 +0 -0
  129. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
  130. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
  131. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
  132. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
  133. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
  134. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
  135. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/templates/workflows/release.yml.j2 +0 -0
  136. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/tool_installer.py +0 -0
  137. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/toolrules.py +0 -0
  138. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/tools.py +0 -0
  139. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/trace.py +0 -0
  140. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/updater.py +0 -0
  141. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/upgrader.py +0 -0
  142. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/validator.py +0 -0
  143. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/vcs/__init__.py +0 -0
  144. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/vcs/base.py +0 -0
  145. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/vcs/bitbucket.py +0 -0
  146. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/vcs/github.py +0 -0
  147. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/vcs/gitlab.py +0 -0
  148. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/vcs_commands.py +0 -0
  149. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/wireframes.py +0 -0
  150. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith/workspace.py +0 -0
  151. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith.egg-info/dependency_links.txt +0 -0
  152. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith.egg-info/entry_points.txt +0 -0
  153. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith.egg-info/requires.txt +0 -0
  154. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/src/specsmith.egg-info/top_level.txt +0 -0
  155. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_CMD_001.py +0 -0
  156. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_auditor.py +0 -0
  157. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_cli.py +0 -0
  158. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_compressor.py +0 -0
  159. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_e2e_nexus.py +0 -0
  160. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_epistemic.py +0 -0
  161. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_importer.py +0 -0
  162. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_integrations.py +0 -0
  163. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_nexus.py +0 -0
  164. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_phase1_4_new.py +0 -0
  165. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_rate_limits.py +0 -0
  166. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_scaffolder.py +0 -0
  167. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_smoke.py +0 -0
  168. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_tools.py +0 -0
  169. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_validator.py +0 -0
  170. {specsmith-0.5.0.dev224 → specsmith-0.5.0.dev225}/tests/test_vcs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specsmith
3
- Version: 0.5.0.dev224
3
+ Version: 0.5.0.dev225
4
4
  Summary: Applied Epistemic Engineering toolkit — AEE agent sessions, execution profiles, FPGA/HDL governance, tool installer, 50+ CLI commands.
5
5
  Author: BitConcepts
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "specsmith"
7
- version = "0.5.0.dev224"
7
+ version = "0.5.0.dev225"
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"
@@ -5532,6 +5532,415 @@ def cloud_spawn(utterance: str, project_dir: str, endpoint: str, dry_run: bool)
5532
5532
  main.add_command(cloud_group)
5533
5533
 
5534
5534
 
5535
+ # ---------------------------------------------------------------------------
5536
+ # Workflow — parameterised command snippets (Warp-style Workflows)
5537
+ # ---------------------------------------------------------------------------
5538
+
5539
+
5540
+ @main.group(name="workflow")
5541
+ def workflow_group() -> None:
5542
+ """Record, list, and run parameterised command snippets.
5543
+
5544
+ Workflows are saved as YAML files under `.specsmith/workflows/<name>.yml`.
5545
+ Each workflow has a name, an optional description, a command template
5546
+ that may contain ``{{ param }}`` placeholders, and a list of accepted
5547
+ params. ``specsmith workflow run <name>`` substitutes the params and
5548
+ executes the resulting command via ``subprocess.run``.
5549
+ """
5550
+
5551
+
5552
+ def _workflows_dir(root: Path) -> Path:
5553
+ d = root / ".specsmith" / "workflows"
5554
+ d.mkdir(parents=True, exist_ok=True)
5555
+ return d
5556
+
5557
+
5558
+ @workflow_group.command(name="record")
5559
+ @click.argument("name")
5560
+ @click.option(
5561
+ "--command",
5562
+ "command",
5563
+ required=True,
5564
+ help="Command template. Use {{ param }} for substitution placeholders.",
5565
+ )
5566
+ @click.option("--description", "description", default="", help="Free-text description.")
5567
+ @click.option(
5568
+ "--param",
5569
+ "params",
5570
+ multiple=True,
5571
+ help="Declared parameter name (repeatable). Substituted at run time.",
5572
+ )
5573
+ @click.option("--project-dir", type=click.Path(exists=True), default=".")
5574
+ def workflow_record(
5575
+ name: str,
5576
+ command: str,
5577
+ description: str,
5578
+ params: tuple[str, ...],
5579
+ project_dir: str,
5580
+ ) -> None:
5581
+ """Save a workflow under .specsmith/workflows/<NAME>.yml."""
5582
+ root = Path(project_dir).resolve()
5583
+ target = _workflows_dir(root) / f"{name}.yml"
5584
+ payload = {
5585
+ "name": name,
5586
+ "description": description,
5587
+ "command": command,
5588
+ "params": list(params),
5589
+ }
5590
+ target.write_text(yaml.safe_dump(payload, sort_keys=False), encoding="utf-8")
5591
+ console.print(f"[green]\u2713[/green] Workflow recorded at {target.relative_to(root)}")
5592
+
5593
+
5594
+ @workflow_group.command(name="list")
5595
+ @click.option("--project-dir", type=click.Path(exists=True), default=".")
5596
+ @click.option("--json", "as_json", is_flag=True, default=False, help="Emit JSON.")
5597
+ def workflow_list(project_dir: str, as_json: bool) -> None:
5598
+ """List workflows recorded for this project."""
5599
+ import json as _json
5600
+
5601
+ root = Path(project_dir).resolve()
5602
+ wf_dir = _workflows_dir(root)
5603
+ items: list[dict[str, Any]] = []
5604
+ for path in sorted(wf_dir.glob("*.yml")):
5605
+ try:
5606
+ data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
5607
+ except yaml.YAMLError:
5608
+ continue
5609
+ items.append(
5610
+ {
5611
+ "name": data.get("name", path.stem),
5612
+ "description": data.get("description", ""),
5613
+ "command": data.get("command", ""),
5614
+ "params": list(data.get("params", [])),
5615
+ }
5616
+ )
5617
+ if as_json:
5618
+ click.echo(_json.dumps(items, indent=2))
5619
+ return
5620
+ if not items:
5621
+ console.print("[dim]No workflows recorded.[/dim]")
5622
+ return
5623
+ for item in items:
5624
+ params = ", ".join(item["params"]) or "(none)"
5625
+ console.print(f"[bold]{item['name']}[/bold] — params: {params}")
5626
+ if item["description"]:
5627
+ console.print(f" {item['description']}")
5628
+ console.print(f" [dim]{item['command']}[/dim]")
5629
+
5630
+
5631
+ @workflow_group.command(name="run")
5632
+ @click.argument("name")
5633
+ @click.option(
5634
+ "--param",
5635
+ "param_assignments",
5636
+ multiple=True,
5637
+ help="Parameter assignment in key=value form (repeatable).",
5638
+ )
5639
+ @click.option(
5640
+ "--dry-run",
5641
+ is_flag=True,
5642
+ default=False,
5643
+ help="Print the resolved command without executing.",
5644
+ )
5645
+ @click.option("--project-dir", type=click.Path(exists=True), default=".")
5646
+ def workflow_run(
5647
+ name: str,
5648
+ param_assignments: tuple[str, ...],
5649
+ dry_run: bool,
5650
+ project_dir: str,
5651
+ ) -> None:
5652
+ """Substitute parameters and execute the recorded workflow."""
5653
+ import re
5654
+ import shlex
5655
+ import subprocess
5656
+
5657
+ root = Path(project_dir).resolve()
5658
+ target = _workflows_dir(root) / f"{name}.yml"
5659
+ if not target.is_file():
5660
+ console.print(f"[red]No workflow named '{name}' at {target}[/red]")
5661
+ raise SystemExit(1)
5662
+ data = yaml.safe_load(target.read_text(encoding="utf-8")) or {}
5663
+ template: str = data.get("command", "")
5664
+ declared = list(data.get("params", []))
5665
+
5666
+ assignments: dict[str, str] = {}
5667
+ for raw in param_assignments:
5668
+ if "=" not in raw:
5669
+ console.print(f"[red]Bad --param value: {raw!r} (expected key=value)[/red]")
5670
+ raise SystemExit(2)
5671
+ key, _, value = raw.partition("=")
5672
+ assignments[key.strip()] = value
5673
+
5674
+ missing = [p for p in declared if p not in assignments]
5675
+ if missing:
5676
+ console.print(f"[red]Missing required params: {', '.join(missing)}[/red]")
5677
+ raise SystemExit(2)
5678
+
5679
+ def _replace(match: re.Match[str]) -> str:
5680
+ key = match.group(1).strip()
5681
+ return assignments.get(key, match.group(0))
5682
+
5683
+ resolved = re.sub(r"\{\{\s*([^}]+?)\s*\}\}", _replace, template)
5684
+
5685
+ if dry_run:
5686
+ console.print(f"[cyan]{resolved}[/cyan]")
5687
+ return
5688
+
5689
+ args = shlex.split(resolved, posix=False) if resolved else []
5690
+ if not args:
5691
+ console.print("[red]Resolved workflow command is empty.[/red]")
5692
+ raise SystemExit(2)
5693
+ raise SystemExit(subprocess.call(args, cwd=str(root))) # noqa: S603
5694
+
5695
+
5696
+ main.add_command(workflow_group)
5697
+
5698
+
5699
+ # ---------------------------------------------------------------------------
5700
+ # History — search across .specsmith/sessions/<id>/turns.jsonl (REQ-120)
5701
+ # ---------------------------------------------------------------------------
5702
+
5703
+
5704
+ @main.group(name="history")
5705
+ def history_group() -> None:
5706
+ """Search and list persistent session memory written by `specsmith chat`."""
5707
+
5708
+
5709
+ def _sessions_dir(root: Path) -> Path:
5710
+ return root / ".specsmith" / "sessions"
5711
+
5712
+
5713
+ @history_group.command(name="list")
5714
+ @click.option("--project-dir", type=click.Path(exists=True), default=".")
5715
+ @click.option("--limit", type=int, default=20, help="Max number of sessions to list.")
5716
+ @click.option("--json", "as_json", is_flag=True, default=False)
5717
+ def history_list(project_dir: str, limit: int, as_json: bool) -> None:
5718
+ """List the N most recent sessions with turn counts."""
5719
+ import json as _json
5720
+
5721
+ root = Path(project_dir).resolve()
5722
+ base = _sessions_dir(root)
5723
+ if not base.is_dir():
5724
+ if as_json:
5725
+ click.echo("[]")
5726
+ else:
5727
+ console.print("[dim]No sessions recorded.[/dim]")
5728
+ return
5729
+ sessions = sorted(
5730
+ (p for p in base.iterdir() if p.is_dir()),
5731
+ key=lambda p: p.stat().st_mtime,
5732
+ reverse=True,
5733
+ )[:limit]
5734
+ items: list[dict[str, Any]] = []
5735
+ for sd in sessions:
5736
+ turns_path = sd / "turns.jsonl"
5737
+ count = 0
5738
+ if turns_path.is_file():
5739
+ with turns_path.open("r", encoding="utf-8") as fh:
5740
+ count = sum(1 for line in fh if line.strip())
5741
+ items.append({"session_id": sd.name, "turns": count, "path": str(turns_path)})
5742
+ if as_json:
5743
+ click.echo(_json.dumps(items, indent=2))
5744
+ return
5745
+ if not items:
5746
+ console.print("[dim]No sessions recorded.[/dim]")
5747
+ return
5748
+ for item in items:
5749
+ console.print(f"[bold]{item['session_id']}[/bold] {item['turns']} turn(s)")
5750
+
5751
+
5752
+ @history_group.command(name="search")
5753
+ @click.argument("query")
5754
+ @click.option("--project-dir", type=click.Path(exists=True), default=".")
5755
+ @click.option("--session", "session", default="", help="Limit to a specific session id.")
5756
+ @click.option("--limit", type=int, default=50, help="Max matching turns to print.")
5757
+ @click.option("--json", "as_json", is_flag=True, default=False)
5758
+ def history_search(
5759
+ query: str,
5760
+ project_dir: str,
5761
+ session: str,
5762
+ limit: int,
5763
+ as_json: bool,
5764
+ ) -> None:
5765
+ """Print turns whose JSON content contains QUERY (case-insensitive substring)."""
5766
+ import json as _json
5767
+
5768
+ root = Path(project_dir).resolve()
5769
+ base = _sessions_dir(root)
5770
+ if not base.is_dir():
5771
+ if as_json:
5772
+ click.echo("[]")
5773
+ return
5774
+ needle = query.lower()
5775
+ targets = [base / session / "turns.jsonl"] if session else sorted(base.rglob("turns.jsonl"))
5776
+ matches: list[dict[str, Any]] = []
5777
+ for path in targets:
5778
+ if not path.is_file():
5779
+ continue
5780
+ with path.open("r", encoding="utf-8") as fh:
5781
+ for raw in fh:
5782
+ if needle not in raw.lower():
5783
+ continue
5784
+ try:
5785
+ turn = _json.loads(raw)
5786
+ except _json.JSONDecodeError:
5787
+ continue
5788
+ matches.append({"session_id": path.parent.name, "turn": turn})
5789
+ if len(matches) >= limit:
5790
+ break
5791
+ if len(matches) >= limit:
5792
+ break
5793
+ if as_json:
5794
+ click.echo(_json.dumps(matches, indent=2))
5795
+ return
5796
+ if not matches:
5797
+ console.print("[dim]No matches.[/dim]")
5798
+ return
5799
+ for hit in matches:
5800
+ console.print(f"[bold]{hit['session_id']}[/bold]: {_json.dumps(hit['turn'])[:200]}")
5801
+
5802
+
5803
+ main.add_command(history_group)
5804
+
5805
+
5806
+ # ---------------------------------------------------------------------------
5807
+ # Drive — user-scoped sync for rules / workflows / notebooks / mcp configs
5808
+ # ---------------------------------------------------------------------------
5809
+
5810
+ _DRIVE_KINDS = {
5811
+ "rules": ("docs/governance",),
5812
+ "workflows": (".specsmith/workflows",),
5813
+ "notebooks": ("docs/notebooks",),
5814
+ "mcp": (".specsmith/mcp.yml",),
5815
+ }
5816
+
5817
+
5818
+ def _drive_root() -> Path:
5819
+ home = Path.home()
5820
+ base = home / ".specsmith" / "drive"
5821
+ base.mkdir(parents=True, exist_ok=True)
5822
+ return base
5823
+
5824
+
5825
+ @main.group(name="drive")
5826
+ def drive_group() -> None:
5827
+ """User-scoped Drive at ~/.specsmith/drive/ for rules / workflows / notebooks.
5828
+
5829
+ The Drive is a local, gitignored mirror of the four kinds of project
5830
+ artefacts that users typically want to share across machines:
5831
+ ``rules`` (docs/governance/*_RULES.md), ``workflows``
5832
+ (.specsmith/workflows/*.yml), ``notebooks`` (docs/notebooks/*.md), and
5833
+ ``mcp`` (.specsmith/mcp.yml). Cloud sync is left to the user's preferred
5834
+ backup tool — Drive is a stable canonical location, not a server.
5835
+ """
5836
+
5837
+
5838
+ @drive_group.command(name="list")
5839
+ @click.option("--json", "as_json", is_flag=True, default=False)
5840
+ def drive_list(as_json: bool) -> None:
5841
+ """Show the contents of ~/.specsmith/drive/ grouped by kind."""
5842
+ import json as _json
5843
+
5844
+ base = _drive_root()
5845
+ items: dict[str, list[str]] = {}
5846
+ for kind in _DRIVE_KINDS:
5847
+ kind_dir = base / kind
5848
+ if not kind_dir.is_dir():
5849
+ items[kind] = []
5850
+ continue
5851
+ items[kind] = sorted(
5852
+ str(p.relative_to(kind_dir)) for p in kind_dir.rglob("*") if p.is_file()
5853
+ )
5854
+ if as_json:
5855
+ click.echo(_json.dumps(items, indent=2))
5856
+ return
5857
+ for kind, paths in items.items():
5858
+ console.print(f"[bold]{kind}[/bold] ({len(paths)} item(s))")
5859
+ for rel in paths:
5860
+ console.print(f" {rel}")
5861
+
5862
+
5863
+ @drive_group.command(name="push")
5864
+ @click.argument("kind", type=click.Choice(sorted(_DRIVE_KINDS.keys())))
5865
+ @click.option("--project-dir", type=click.Path(exists=True), default=".")
5866
+ def drive_push(kind: str, project_dir: str) -> None:
5867
+ """Copy this project's KIND artefacts into ~/.specsmith/drive/KIND/."""
5868
+ import shutil
5869
+
5870
+ root = Path(project_dir).resolve()
5871
+ base = _drive_root() / kind
5872
+ base.mkdir(parents=True, exist_ok=True)
5873
+ sources = _DRIVE_KINDS[kind]
5874
+ copied = 0
5875
+ for rel in sources:
5876
+ src = root / rel
5877
+ if not src.exists():
5878
+ continue
5879
+ if src.is_file():
5880
+ shutil.copy2(src, base / src.name)
5881
+ copied += 1
5882
+ continue
5883
+ for path in src.rglob("*"):
5884
+ if not path.is_file():
5885
+ continue
5886
+ target = base / path.relative_to(src)
5887
+ target.parent.mkdir(parents=True, exist_ok=True)
5888
+ shutil.copy2(path, target)
5889
+ copied += 1
5890
+ console.print(f"[green]\u2713[/green] Pushed {copied} file(s) to {base}")
5891
+
5892
+
5893
+ @drive_group.command(name="pull")
5894
+ @click.argument("kind", type=click.Choice(sorted(_DRIVE_KINDS.keys())))
5895
+ @click.option("--project-dir", type=click.Path(exists=True), default=".")
5896
+ @click.option("--force", is_flag=True, default=False, help="Overwrite existing project files.")
5897
+ def drive_pull(kind: str, project_dir: str, force: bool) -> None:
5898
+ """Copy KIND artefacts from ~/.specsmith/drive/ into this project.
5899
+
5900
+ Existing project files are preserved unless --force is supplied.
5901
+ """
5902
+ import shutil
5903
+
5904
+ root = Path(project_dir).resolve()
5905
+ base = _drive_root() / kind
5906
+ if not base.is_dir():
5907
+ console.print(f"[yellow]Drive has no {kind!r} entries yet.[/yellow]")
5908
+ return
5909
+ target_root = root / _DRIVE_KINDS[kind][0]
5910
+ pulled = skipped = 0
5911
+ if base.is_dir() and target_root.suffix == "":
5912
+ target_root.mkdir(parents=True, exist_ok=True)
5913
+ for path in base.rglob("*"):
5914
+ if not path.is_file():
5915
+ continue
5916
+ dest = target_root / path.relative_to(base)
5917
+ dest.parent.mkdir(parents=True, exist_ok=True)
5918
+ if dest.exists() and not force:
5919
+ skipped += 1
5920
+ continue
5921
+ shutil.copy2(path, dest)
5922
+ pulled += 1
5923
+ else:
5924
+ # Single-file kind (e.g. mcp.yml).
5925
+ for path in base.iterdir():
5926
+ if not path.is_file():
5927
+ continue
5928
+ dest = root / _DRIVE_KINDS[kind][0]
5929
+ if dest.exists() and not force:
5930
+ skipped += 1
5931
+ continue
5932
+ dest.parent.mkdir(parents=True, exist_ok=True)
5933
+ shutil.copy2(path, dest)
5934
+ pulled += 1
5935
+ console.print(
5936
+ f"[green]\u2713[/green] Pulled {pulled} file(s) into {target_root}; "
5937
+ f"skipped {skipped} (use --force to overwrite)."
5938
+ )
5939
+
5940
+
5941
+ main.add_command(drive_group)
5942
+
5943
+
5535
5944
  # ---------------------------------------------------------------------------
5536
5945
  # AG2 Agent Shell
5537
5946
  # ---------------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specsmith
3
- Version: 0.5.0.dev224
3
+ Version: 0.5.0.dev225
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
@@ -152,6 +152,7 @@ src/specsmith/vcs/gitlab.py
152
152
  tests/test_CMD_001.py
153
153
  tests/test_auditor.py
154
154
  tests/test_cli.py
155
+ tests/test_cli_workflows_history_drive.py
155
156
  tests/test_compressor.py
156
157
  tests/test_e2e_nexus.py
157
158
  tests/test_epistemic.py