specsmith 0.6.0.dev232__tar.gz → 0.6.0.dev233__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 (184) hide show
  1. {specsmith-0.6.0.dev232/src/specsmith.egg-info → specsmith-0.6.0.dev233}/PKG-INFO +6 -1
  2. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/pyproject.toml +12 -1
  3. specsmith-0.6.0.dev233/src/specsmith/block_export.py +106 -0
  4. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/cli.py +123 -0
  5. specsmith-0.6.0.dev233/src/specsmith/cloud_serve.py +150 -0
  6. specsmith-0.6.0.dev233/src/specsmith/drive.py +126 -0
  7. specsmith-0.6.0.dev233/src/specsmith/history_search.py +159 -0
  8. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233/src/specsmith.egg-info}/PKG-INFO +6 -1
  9. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith.egg-info/SOURCES.txt +6 -1
  10. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith.egg-info/requires.txt +7 -0
  11. specsmith-0.6.0.dev233/tests/test_warp_parity.py +421 -0
  12. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/LICENSE +0 -0
  13. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/README.md +0 -0
  14. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/setup.cfg +0 -0
  15. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/epistemic/__init__.py +0 -0
  16. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/epistemic/belief.py +0 -0
  17. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/epistemic/certainty.py +0 -0
  18. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/epistemic/failure_graph.py +0 -0
  19. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/epistemic/py.typed +0 -0
  20. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/epistemic/recovery.py +0 -0
  21. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/epistemic/session.py +0 -0
  22. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/epistemic/stress_tester.py +0 -0
  23. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/epistemic/trace.py +0 -0
  24. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/__init__.py +0 -0
  25. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/__main__.py +0 -0
  26. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/__init__.py +0 -0
  27. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/broker.py +0 -0
  28. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/chat_runner.py +0 -0
  29. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/cleanup.py +0 -0
  30. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/events.py +0 -0
  31. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/indexer.py +0 -0
  32. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/mcp.py +0 -0
  33. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/memory.py +0 -0
  34. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/orchestrator.py +0 -0
  35. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/repl.py +0 -0
  36. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/router.py +0 -0
  37. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/rules.py +0 -0
  38. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/safety.py +0 -0
  39. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/suggester.py +0 -0
  40. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/tools.py +0 -0
  41. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/agent/verifier.py +0 -0
  42. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/architect.py +0 -0
  43. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/auditor.py +0 -0
  44. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/auth.py +0 -0
  45. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/commands/__init__.py +0 -0
  46. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/compressor.py +0 -0
  47. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/config.py +0 -0
  48. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/console_utils.py +0 -0
  49. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/credit_analyzer.py +0 -0
  50. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/credits.py +0 -0
  51. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/differ.py +0 -0
  52. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/doctor.py +0 -0
  53. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/epistemic/__init__.py +0 -0
  54. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/epistemic/belief.py +0 -0
  55. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/epistemic/certainty.py +0 -0
  56. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/epistemic/failure_graph.py +0 -0
  57. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/epistemic/recovery.py +0 -0
  58. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/epistemic/stress_tester.py +0 -0
  59. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/executor.py +0 -0
  60. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/exporter.py +0 -0
  61. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/gui/__init__.py +0 -0
  62. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/gui/app.py +0 -0
  63. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/gui/main_window.py +0 -0
  64. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/gui/session_tab.py +0 -0
  65. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/gui/theme.py +0 -0
  66. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/gui/widgets/__init__.py +0 -0
  67. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/gui/widgets/chat_view.py +0 -0
  68. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/gui/widgets/input_bar.py +0 -0
  69. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/gui/widgets/provider_bar.py +0 -0
  70. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/gui/widgets/token_meter.py +0 -0
  71. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/gui/widgets/tool_panel.py +0 -0
  72. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/gui/widgets/update_checker.py +0 -0
  73. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/gui/worker.py +0 -0
  74. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/importer.py +0 -0
  75. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/integrations/__init__.py +0 -0
  76. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/integrations/agent_skill.py +0 -0
  77. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/integrations/aider.py +0 -0
  78. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/integrations/base.py +0 -0
  79. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/integrations/claude_code.py +0 -0
  80. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/integrations/copilot.py +0 -0
  81. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/integrations/cursor.py +0 -0
  82. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/integrations/gemini.py +0 -0
  83. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/integrations/windsurf.py +0 -0
  84. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/languages.py +0 -0
  85. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/ledger.py +0 -0
  86. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/patent.py +0 -0
  87. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/phase.py +0 -0
  88. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/plugins.py +0 -0
  89. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/profiles.py +0 -0
  90. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/rate_limits.py +0 -0
  91. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/releaser.py +0 -0
  92. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/requirements.py +0 -0
  93. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/requirements_parser.py +0 -0
  94. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/retrieval.py +0 -0
  95. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/scaffolder.py +0 -0
  96. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/serve.py +0 -0
  97. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/session.py +0 -0
  98. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/skills.py +0 -0
  99. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/agents.md.j2 +0 -0
  100. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/bug_report.md.j2 +0 -0
  101. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/code_of_conduct.md.j2 +0 -0
  102. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/contributing.md.j2 +0 -0
  103. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/feature_request.md.j2 +0 -0
  104. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/license-Apache-2.0.j2 +0 -0
  105. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/license-MIT.j2 +0 -0
  106. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/pull_request_template.md.j2 +0 -0
  107. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/community/security.md.j2 +0 -0
  108. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
  109. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/docs/mkdocs.yml.j2 +0 -0
  110. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/docs/readthedocs.yaml.j2 +0 -0
  111. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
  112. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
  113. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/editorconfig.j2 +0 -0
  114. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/gitattributes.j2 +0 -0
  115. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/gitignore.j2 +0 -0
  116. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/go/go.mod.j2 +0 -0
  117. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/go/main.go.j2 +0 -0
  118. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/belief-registry.md.j2 +0 -0
  119. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
  120. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
  121. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/epistemic-axioms.md.j2 +0 -0
  122. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/failure-modes.md.j2 +0 -0
  123. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/lifecycle.md.j2 +0 -0
  124. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/roles.md.j2 +0 -0
  125. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/rules.md.j2 +0 -0
  126. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/session-protocol.md.j2 +0 -0
  127. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/uncertainty-map.md.j2 +0 -0
  128. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/governance/verification.md.j2 +0 -0
  129. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/js/package.json.j2 +0 -0
  130. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/ledger.md.j2 +0 -0
  131. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/python/cli.py.j2 +0 -0
  132. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/python/init.py.j2 +0 -0
  133. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/python/pyproject.toml.j2 +0 -0
  134. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/readme.md.j2 +0 -0
  135. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/rust/Cargo.toml.j2 +0 -0
  136. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/rust/main.rs.j2 +0 -0
  137. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
  138. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
  139. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
  140. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
  141. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
  142. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
  143. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/templates/workflows/release.yml.j2 +0 -0
  144. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/tool_installer.py +0 -0
  145. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/toolrules.py +0 -0
  146. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/tools.py +0 -0
  147. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/trace.py +0 -0
  148. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/updater.py +0 -0
  149. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/upgrader.py +0 -0
  150. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/validator.py +0 -0
  151. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/vcs/__init__.py +0 -0
  152. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/vcs/base.py +0 -0
  153. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/vcs/bitbucket.py +0 -0
  154. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/vcs/github.py +0 -0
  155. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/vcs/gitlab.py +0 -0
  156. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/vcs_commands.py +0 -0
  157. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/wireframes.py +0 -0
  158. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith/workspace.py +0 -0
  159. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith.egg-info/dependency_links.txt +0 -0
  160. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith.egg-info/entry_points.txt +0 -0
  161. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/src/specsmith.egg-info/top_level.txt +0 -0
  162. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_CMD_001.py +0 -0
  163. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_auditor.py +0 -0
  164. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_chat_diff_decision.py +0 -0
  165. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_chat_stdin_protocol.py +0 -0
  166. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_cli.py +0 -0
  167. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_cli_workflows_history_drive.py +0 -0
  168. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_compressor.py +0 -0
  169. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_e2e_nexus.py +0 -0
  170. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_epistemic.py +0 -0
  171. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_importer.py +0 -0
  172. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_integrations.py +0 -0
  173. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_mcp_client.py +0 -0
  174. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_nexus.py +0 -0
  175. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_phase1_4_new.py +0 -0
  176. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_phase34_completion.py +0 -0
  177. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_rate_limits.py +0 -0
  178. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_scaffolder.py +0 -0
  179. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_skill_marketplace.py +0 -0
  180. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_smoke.py +0 -0
  181. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_suggester.py +0 -0
  182. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_tools.py +0 -0
  183. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_validator.py +0 -0
  184. {specsmith-0.6.0.dev232 → specsmith-0.6.0.dev233}/tests/test_vcs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specsmith
3
- Version: 0.6.0.dev232
3
+ Version: 0.6.0.dev233
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
@@ -53,6 +53,11 @@ Provides-Extra: gui
53
53
  Requires-Dist: PySide6>=6.6; extra == "gui"
54
54
  Provides-Extra: ag2
55
55
  Requires-Dist: ag2[ollama]; extra == "ag2"
56
+ Provides-Extra: history-semantic
57
+ Requires-Dist: sentence-transformers>=2.2; extra == "history-semantic"
58
+ Requires-Dist: numpy>=1.24; extra == "history-semantic"
59
+ Provides-Extra: voice
60
+ Requires-Dist: whisper-cpp-python>=0.2; extra == "voice"
56
61
  Provides-Extra: agent
57
62
  Requires-Dist: anthropic>=0.56; extra == "agent"
58
63
  Requires-Dist: openai>=1.0; extra == "agent"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "specsmith"
7
- version = "0.6.0.dev232"
7
+ version = "0.6.0.dev233"
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"
@@ -67,6 +67,11 @@ mistral = ["openai>=1.0"] # Mistral uses the openai SDK pointed at api.mistral.
67
67
  gui = ["PySide6>=6.6"]
68
68
  # AG2 agent shell (Planner/Builder/Verifier over Ollama)
69
69
  ag2 = ["ag2[ollama]"]
70
+ # Optional semantic backend for `specsmith history search --semantic` (REQ-135).
71
+ # Falls back gracefully to keyword matching if these are not installed.
72
+ history-semantic = ["sentence-transformers>=2.2", "numpy>=1.24"]
73
+ # Optional whisper-cpp wrapper for the voice agent input (REQ-141).
74
+ voice = ["whisper-cpp-python>=0.2"]
70
75
  # Install all optional LLM providers
71
76
  agent = ["anthropic>=0.56", "openai>=1.0"]
72
77
  # Convenience bundle: everything
@@ -138,6 +143,12 @@ module = [
138
143
  "yaml.*",
139
144
  "keyring", # optional OS credential store; stubs not published
140
145
  "keyring.*",
146
+ "numpy", # optional [history-semantic] extra (REQ-135)
147
+ "numpy.*",
148
+ "sentence_transformers", # optional [history-semantic] extra (REQ-135)
149
+ "sentence_transformers.*",
150
+ "whisper_cpp_python", # optional [voice] extra (REQ-141)
151
+ "whisper_cpp_python.*",
141
152
  ]
142
153
  ignore_missing_imports = true
143
154
 
@@ -0,0 +1,106 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
3
+ """Per-block share/export for `specsmith chat` (REQ-134).
4
+
5
+ Reads ``.specsmith/sessions/<session_id>/events.jsonl`` (the chat replay log
6
+ or, fallback, ``turns.jsonl``) and slices a single block out as a
7
+ self-contained Markdown / JSON / HTML snippet.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import html
13
+ import json
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+
18
+ def _events_path(project_dir: Path, session_id: str) -> Path | None:
19
+ base = project_dir / ".specsmith" / "sessions" / session_id
20
+ candidates = [
21
+ base / "events.jsonl",
22
+ base / "turns.jsonl",
23
+ ]
24
+ for c in candidates:
25
+ if c.is_file():
26
+ return c
27
+ return None
28
+
29
+
30
+ def _read_events(events_path: Path) -> list[dict[str, Any]]:
31
+ out: list[dict[str, Any]] = []
32
+ for line in events_path.read_text(encoding="utf-8").splitlines():
33
+ line = line.strip()
34
+ if not line:
35
+ continue
36
+ try:
37
+ obj = json.loads(line)
38
+ except ValueError:
39
+ continue
40
+ if isinstance(obj, dict):
41
+ out.append(obj)
42
+ return out
43
+
44
+
45
+ def slice_block(events: list[dict[str, Any]], block_id: str) -> list[dict[str, Any]]:
46
+ """Return all events tagged with ``block_id``, plus the bracketing
47
+ block_start/block_complete events that defined it.
48
+ """
49
+ out: list[dict[str, Any]] = []
50
+ for evt in events:
51
+ if evt.get("block_id") == block_id or evt.get("id") == block_id:
52
+ out.append(evt)
53
+ return out
54
+
55
+
56
+ def export_block(
57
+ project_dir: Path,
58
+ session_id: str,
59
+ block_id: str,
60
+ *,
61
+ fmt: str = "md",
62
+ ) -> str:
63
+ """Export the events for ``block_id`` as a string in ``fmt``.
64
+
65
+ Raises FileNotFoundError if no session log exists.
66
+ Raises KeyError if the block is not found.
67
+ """
68
+ events_path = _events_path(project_dir, session_id)
69
+ if events_path is None:
70
+ raise FileNotFoundError(f"No session log for {session_id} in {project_dir}")
71
+ events = _read_events(events_path)
72
+ matching = slice_block(events, block_id)
73
+ if not matching:
74
+ raise KeyError(f"block_id {block_id} not found in session {session_id}")
75
+ if fmt == "json":
76
+ return json.dumps(matching, indent=2)
77
+ if fmt == "html":
78
+ rows = "".join(
79
+ f"<li><pre>{html.escape(json.dumps(evt, indent=2))}</pre></li>" for evt in matching
80
+ )
81
+ return (
82
+ f"<!DOCTYPE html><html><head><meta charset='utf-8'>"
83
+ f"<title>specsmith block {html.escape(block_id)}</title></head>"
84
+ f"<body><h1>Block {html.escape(block_id)}</h1>"
85
+ f"<p>session {html.escape(session_id)}</p><ol>{rows}</ol></body></html>"
86
+ )
87
+ # default: markdown
88
+ lines: list[str] = [
89
+ f"# Block `{block_id}`",
90
+ f"_session_: `{session_id}`",
91
+ "",
92
+ ]
93
+ for evt in matching:
94
+ kind = str(evt.get("type", "event"))
95
+ lines.append(f"## {kind}")
96
+ if "text" in evt:
97
+ lines.append(str(evt["text"]))
98
+ else:
99
+ lines.append("```json")
100
+ lines.append(json.dumps(evt, indent=2))
101
+ lines.append("```")
102
+ lines.append("")
103
+ return "\n".join(lines)
104
+
105
+
106
+ __all__ = ["export_block", "slice_block"]
@@ -4440,6 +4440,129 @@ def info_cmd(as_json: bool, section: str) -> None:
4440
4440
  # ---------------------------------------------------------------------------
4441
4441
 
4442
4442
 
4443
+ # ---------------------------------------------------------------------------
4444
+ # specsmith chat-export-block — self-contained block share (REQ-134)
4445
+ # ---------------------------------------------------------------------------
4446
+ #
4447
+ # This is exposed at the top level (rather than under ``chat``) because the
4448
+ # existing ``specsmith chat <utterance>`` command takes a positional argument
4449
+ # and cannot simultaneously act as a Click group.
4450
+
4451
+
4452
+ @main.command(name="chat-export-block")
4453
+ @click.option("--project-dir", type=click.Path(exists=True), default=".")
4454
+ @click.option("--session-id", "session_id", required=True)
4455
+ @click.option("--block-id", "block_id", required=True)
4456
+ @click.option(
4457
+ "--format",
4458
+ "fmt",
4459
+ type=click.Choice(["md", "json", "html"]),
4460
+ default="md",
4461
+ )
4462
+ def chat_export_block_cmd(project_dir: str, session_id: str, block_id: str, fmt: str) -> None:
4463
+ """Export one chat block as a self-contained snippet (REQ-134)."""
4464
+ from specsmith.block_export import export_block
4465
+
4466
+ try:
4467
+ out = export_block(
4468
+ Path(project_dir).resolve(),
4469
+ session_id,
4470
+ block_id,
4471
+ fmt=fmt,
4472
+ )
4473
+ except FileNotFoundError as exc:
4474
+ console.print(f"[red]{exc}[/red]")
4475
+ raise SystemExit(1) from exc
4476
+ except KeyError as exc:
4477
+ console.print(f"[red]{exc}[/red]")
4478
+ raise SystemExit(1) from exc
4479
+ click.echo(out)
4480
+
4481
+
4482
+ # ---------------------------------------------------------------------------
4483
+ # specsmith cloud serve — reference cloud-agent receiver (REQ-136)
4484
+ # ---------------------------------------------------------------------------
4485
+
4486
+
4487
+ @main.command(name="cloud-serve")
4488
+ @click.option("--host", default="127.0.0.1")
4489
+ @click.option("--port", type=int, default=9000)
4490
+ @click.option("--token", default="", help="Optional bearer token.")
4491
+ @click.option("--allow-cidr", default="", help="CIDR range required to bind non-loopback.")
4492
+ def cloud_serve_cmd(host: str, port: int, token: str, allow_cidr: str) -> None:
4493
+ """Run the reference cloud-agent receiver (REQ-136).
4494
+
4495
+ Accepts POST /spawn with a JSON manifest, persists it under
4496
+ ~/.specsmith/cloud-runs/<run_id>/manifest.json, and returns 202 with
4497
+ a stream_url placeholder.
4498
+ """
4499
+ from specsmith.cloud_serve import CloudReceiverConfig, make_server
4500
+
4501
+ config = CloudReceiverConfig(host=host, port=port, token=token, allow_cidr=allow_cidr)
4502
+ try:
4503
+ server = make_server(config)
4504
+ except RuntimeError as exc:
4505
+ console.print(f"[red]{exc}[/red]")
4506
+ raise SystemExit(2) from exc
4507
+ console.print(
4508
+ f"[bold]specsmith cloud serve[/bold] on http://{config.host}:{config.port}\n"
4509
+ f" storage: {config.storage_dir}\n"
4510
+ f" token: {'(set)' if token else '(none)'}\n"
4511
+ " Press Ctrl+C to stop."
4512
+ )
4513
+ try:
4514
+ server.serve_forever()
4515
+ except KeyboardInterrupt:
4516
+ console.print("\n[dim]cloud serve stopped.[/dim]")
4517
+ server.server_close()
4518
+
4519
+
4520
+ # ---------------------------------------------------------------------------
4521
+ # specsmith api-surface — 1.0 stability snapshot (REQ-140)
4522
+ # ---------------------------------------------------------------------------
4523
+
4524
+
4525
+ @main.command(name="api-surface")
4526
+ @click.option(
4527
+ "--snapshot",
4528
+ type=click.Path(),
4529
+ default="",
4530
+ help="Write the current public surface to this JSON file.",
4531
+ )
4532
+ def api_surface_cmd(snapshot: str) -> None:
4533
+ """Print the frozen public CLI/API surface as JSON (REQ-140)."""
4534
+ import json as _json
4535
+
4536
+ surface = {
4537
+ "cli_commands": sorted(
4538
+ cmd_name for cmd_name in main.commands if not cmd_name.startswith("_")
4539
+ ),
4540
+ "exit_codes": {
4541
+ "preflight_accepted": 0,
4542
+ "preflight_needs_clarification": 2,
4543
+ "preflight_blocked": 3,
4544
+ "verify_ok": 0,
4545
+ "verify_retry": 2,
4546
+ "verify_stop": 3,
4547
+ },
4548
+ "event_types": [
4549
+ "block_start",
4550
+ "block_complete",
4551
+ "token",
4552
+ "plan_step",
4553
+ "tool_call",
4554
+ "tool_request",
4555
+ "tool_result",
4556
+ "diff",
4557
+ "task_complete",
4558
+ ],
4559
+ }
4560
+ payload = _json.dumps(surface, indent=2, sort_keys=True)
4561
+ if snapshot:
4562
+ Path(snapshot).write_text(payload, encoding="utf-8")
4563
+ click.echo(payload)
4564
+
4565
+
4443
4566
  # ---------------------------------------------------------------------------
4444
4567
  # specsmith suggest-command — NL-to-command suggester (REQ-131)
4445
4568
  # ---------------------------------------------------------------------------
@@ -0,0 +1,150 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
3
+ """Reference cloud-agent receiver for `specsmith cloud spawn` (REQ-136).
4
+
5
+ A minimal stdlib HTTP server that accepts manifest-only POSTs at ``/spawn``
6
+ and acks them. The full streaming-back-of-results contract is documented
7
+ but kept narrow (and intentionally local-only) so we ship a working
8
+ endpoint without baking in vendor coupling.
9
+
10
+ Auth model: optional ``Authorization: Bearer <token>``. When the server
11
+ is started with ``--token``, every request must present it.
12
+ Defense-in-depth: the server refuses to bind to any address other than
13
+ ``127.0.0.1`` unless explicitly given ``--host`` AND ``--allow-cidr``.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import ipaddress
19
+ import json
20
+ import threading
21
+ from dataclasses import dataclass, field
22
+ from http.server import BaseHTTPRequestHandler, HTTPServer
23
+ from pathlib import Path
24
+ from typing import Any
25
+
26
+
27
+ @dataclass
28
+ class CloudReceiverConfig:
29
+ host: str = "127.0.0.1"
30
+ port: int = 9000
31
+ token: str = ""
32
+ allow_cidr: str = ""
33
+ storage_dir: Path = field(default_factory=lambda: Path.home() / ".specsmith" / "cloud-runs")
34
+
35
+
36
+ class _Handler(BaseHTTPRequestHandler):
37
+ config: CloudReceiverConfig = CloudReceiverConfig()
38
+
39
+ # noqa: N802 -- BaseHTTPRequestHandler API.
40
+ def do_POST(self) -> None: # noqa: N802
41
+ if not self._authorize():
42
+ self._respond(401, {"error": "unauthorized"})
43
+ return
44
+ if self.path != "/spawn":
45
+ self._respond(404, {"error": f"unknown path {self.path}"})
46
+ return
47
+ length = int(self.headers.get("Content-Length", "0") or "0")
48
+ body = self.rfile.read(length) if length else b""
49
+ try:
50
+ payload = json.loads(body.decode("utf-8") or "{}")
51
+ except ValueError:
52
+ self._respond(400, {"error": "invalid json"})
53
+ return
54
+ run_id = str(payload.get("run_id", "")).strip() or _new_run_id()
55
+ target = self.config.storage_dir / run_id
56
+ try:
57
+ target.mkdir(parents=True, exist_ok=True)
58
+ (target / "manifest.json").write_text(
59
+ json.dumps(payload, indent=2),
60
+ encoding="utf-8",
61
+ )
62
+ except OSError as exc:
63
+ self._respond(500, {"error": f"storage failed: {exc}"})
64
+ return
65
+ self._respond(
66
+ 202,
67
+ {
68
+ "run_id": run_id,
69
+ "status": "accepted",
70
+ "stream_url": f"/runs/{run_id}/events",
71
+ },
72
+ )
73
+
74
+ def do_GET(self) -> None: # noqa: N802
75
+ if not self._authorize():
76
+ self._respond(401, {"error": "unauthorized"})
77
+ return
78
+ if self.path == "/health":
79
+ self._respond(200, {"ok": True})
80
+ return
81
+ self._respond(404, {"error": f"unknown path {self.path}"})
82
+
83
+ def log_message(self, format: str, *args: Any) -> None: # noqa: A002
84
+ # Quiet by default — caller sees JSON responses.
85
+ return
86
+
87
+ # ── helpers ───────────────────────────────────────────────────────────
88
+
89
+ def _authorize(self) -> bool:
90
+ if self.config.token:
91
+ header = self.headers.get("Authorization", "")
92
+ if header != f"Bearer {self.config.token}":
93
+ return False
94
+ if self.config.allow_cidr:
95
+ try:
96
+ net = ipaddress.ip_network(self.config.allow_cidr, strict=False)
97
+ client = ipaddress.ip_address(self.client_address[0])
98
+ if client not in net:
99
+ return False
100
+ except (ValueError, TypeError):
101
+ return False
102
+ return True
103
+
104
+ def _respond(self, status: int, payload: dict[str, Any]) -> None:
105
+ body = json.dumps(payload).encode("utf-8")
106
+ self.send_response(status)
107
+ self.send_header("Content-Type", "application/json")
108
+ self.send_header("Content-Length", str(len(body)))
109
+ self.end_headers()
110
+ self.wfile.write(body)
111
+
112
+
113
+ def _new_run_id() -> str:
114
+ import uuid
115
+
116
+ return f"cloud_{uuid.uuid4().hex[:12]}"
117
+
118
+
119
+ def _validate_host(config: CloudReceiverConfig) -> None:
120
+ if config.host not in {"127.0.0.1", "::1", "localhost"} and not config.allow_cidr:
121
+ raise RuntimeError(
122
+ "specsmith cloud serve refuses to bind to a non-loopback address "
123
+ "unless --allow-cidr is also set. This is a security guardrail."
124
+ )
125
+
126
+
127
+ def make_server(config: CloudReceiverConfig) -> HTTPServer:
128
+ _validate_host(config)
129
+ config.storage_dir.mkdir(parents=True, exist_ok=True)
130
+
131
+ class _Bound(_Handler):
132
+ pass
133
+
134
+ _Bound.config = config
135
+ return HTTPServer((config.host, config.port), _Bound)
136
+
137
+
138
+ def run_in_thread(config: CloudReceiverConfig) -> tuple[HTTPServer, threading.Thread]:
139
+ """Start the server in a background thread; useful for tests."""
140
+ server = make_server(config)
141
+ thread = threading.Thread(target=server.serve_forever, daemon=True)
142
+ thread.start()
143
+ return server, thread
144
+
145
+
146
+ __all__ = [
147
+ "CloudReceiverConfig",
148
+ "make_server",
149
+ "run_in_thread",
150
+ ]
@@ -0,0 +1,126 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
3
+ """Specsmith Drive (REQ-133) — sync rules/workflows/notebooks across machines.
4
+
5
+ Backend agnostic: the default backend is a local filesystem mirror under
6
+ ``~/.specsmith/drive/`` which the user can ``git push`` themselves. An
7
+ HTTP backend is documented but not bundled (see ``examples/drive_http_server.py``
8
+ once it exists).
9
+
10
+ Supported artifact kinds:
11
+ * ``rules`` — files under ``docs/governance/*_RULES.md``
12
+ * ``workflows`` — files under ``.specsmith/workflows/*.yml``
13
+ * ``notebooks`` — files under ``docs/notebooks/*.md``
14
+
15
+ Each project's artifacts go into ``<drive>/<project_name>/<kind>/<file>``.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import shutil
21
+ from dataclasses import dataclass
22
+ from pathlib import Path
23
+
24
+ KINDS = ("rules", "workflows", "notebooks")
25
+
26
+
27
+ def default_drive_dir() -> Path:
28
+ return Path.home() / ".specsmith" / "drive"
29
+
30
+
31
+ def _kind_sources(project_dir: Path) -> dict[str, list[Path]]:
32
+ return {
33
+ "rules": sorted((project_dir / "docs" / "governance").glob("*_RULES.md"))
34
+ if (project_dir / "docs" / "governance").is_dir()
35
+ else [],
36
+ "workflows": sorted((project_dir / ".specsmith" / "workflows").glob("*.yml"))
37
+ if (project_dir / ".specsmith" / "workflows").is_dir()
38
+ else [],
39
+ "notebooks": sorted((project_dir / "docs" / "notebooks").glob("*.md"))
40
+ if (project_dir / "docs" / "notebooks").is_dir()
41
+ else [],
42
+ }
43
+
44
+
45
+ def _kind_dest(drive_dir: Path, project_name: str, kind: str) -> Path:
46
+ return drive_dir / project_name / kind
47
+
48
+
49
+ @dataclass
50
+ class DriveResult:
51
+ pushed: list[str]
52
+ pulled: list[str]
53
+ skipped: list[str]
54
+ errors: list[str]
55
+
56
+
57
+ def push(project_dir: Path, drive_dir: Path | None = None) -> DriveResult:
58
+ """Mirror project artifacts into the drive directory."""
59
+ drive_dir = drive_dir or default_drive_dir()
60
+ project_name = project_dir.name
61
+ pushed: list[str] = []
62
+ skipped: list[str] = []
63
+ errors: list[str] = []
64
+ for kind, files in _kind_sources(project_dir).items():
65
+ dest = _kind_dest(drive_dir, project_name, kind)
66
+ dest.mkdir(parents=True, exist_ok=True)
67
+ for src in files:
68
+ try:
69
+ shutil.copy2(src, dest / src.name)
70
+ pushed.append(f"{kind}/{src.name}")
71
+ except OSError as exc:
72
+ errors.append(f"{kind}/{src.name}: {exc}")
73
+ if not files:
74
+ skipped.append(f"{kind} (no source files)")
75
+ return DriveResult(pushed=pushed, pulled=[], skipped=skipped, errors=errors)
76
+
77
+
78
+ def pull(project_dir: Path, drive_dir: Path | None = None) -> DriveResult:
79
+ """Mirror drive artifacts back into the project."""
80
+ drive_dir = drive_dir or default_drive_dir()
81
+ project_name = project_dir.name
82
+ pulled: list[str] = []
83
+ skipped: list[str] = []
84
+ errors: list[str] = []
85
+ project_targets = {
86
+ "rules": project_dir / "docs" / "governance",
87
+ "workflows": project_dir / ".specsmith" / "workflows",
88
+ "notebooks": project_dir / "docs" / "notebooks",
89
+ }
90
+ for kind, target in project_targets.items():
91
+ src_dir = _kind_dest(drive_dir, project_name, kind)
92
+ if not src_dir.is_dir():
93
+ skipped.append(f"{kind} (no drive entry)")
94
+ continue
95
+ target.mkdir(parents=True, exist_ok=True)
96
+ for src in sorted(src_dir.iterdir()):
97
+ if not src.is_file():
98
+ continue
99
+ try:
100
+ shutil.copy2(src, target / src.name)
101
+ pulled.append(f"{kind}/{src.name}")
102
+ except OSError as exc:
103
+ errors.append(f"{kind}/{src.name}: {exc}")
104
+ return DriveResult(pushed=[], pulled=pulled, skipped=skipped, errors=errors)
105
+
106
+
107
+ def listing(drive_dir: Path | None = None) -> dict[str, dict[str, list[str]]]:
108
+ """Return ``{project: {kind: [filenames]}}`` for everything in the drive."""
109
+ drive_dir = drive_dir or default_drive_dir()
110
+ out: dict[str, dict[str, list[str]]] = {}
111
+ if not drive_dir.is_dir():
112
+ return out
113
+ for project_path in sorted(drive_dir.iterdir()):
114
+ if not project_path.is_dir():
115
+ continue
116
+ kinds: dict[str, list[str]] = {}
117
+ for kind in KINDS:
118
+ d = project_path / kind
119
+ if d.is_dir():
120
+ kinds[kind] = sorted(p.name for p in d.iterdir() if p.is_file())
121
+ if kinds:
122
+ out[project_path.name] = kinds
123
+ return out
124
+
125
+
126
+ __all__ = ["DriveResult", "KINDS", "default_drive_dir", "listing", "pull", "push"]