devsper 2.1.6__py3-none-any.whl

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 (375) hide show
  1. devsper/__init__.py +14 -0
  2. devsper/agents/a2a/__init__.py +27 -0
  3. devsper/agents/a2a/client.py +126 -0
  4. devsper/agents/a2a/discovery.py +24 -0
  5. devsper/agents/a2a/server.py +128 -0
  6. devsper/agents/a2a/tool_adapter.py +68 -0
  7. devsper/agents/a2a/types.py +49 -0
  8. devsper/agents/agent.py +602 -0
  9. devsper/agents/critic.py +80 -0
  10. devsper/agents/message_bus.py +124 -0
  11. devsper/agents/roles.py +181 -0
  12. devsper/agents/run_agent.py +78 -0
  13. devsper/analytics/__init__.py +5 -0
  14. devsper/analytics/tool_analytics.py +78 -0
  15. devsper/audit/__init__.py +5 -0
  16. devsper/audit/logger.py +214 -0
  17. devsper/bus/__init__.py +29 -0
  18. devsper/bus/backends/__init__.py +5 -0
  19. devsper/bus/backends/base.py +38 -0
  20. devsper/bus/backends/memory.py +55 -0
  21. devsper/bus/backends/redis.py +146 -0
  22. devsper/bus/message.py +56 -0
  23. devsper/bus/schema_version.py +3 -0
  24. devsper/bus/topics.py +19 -0
  25. devsper/cache/__init__.py +6 -0
  26. devsper/cache/embedding_index.py +98 -0
  27. devsper/cache/hashing.py +24 -0
  28. devsper/cache/store.py +153 -0
  29. devsper/cache/task_cache.py +191 -0
  30. devsper/cli/__init__.py +6 -0
  31. devsper/cli/commands/reg.py +733 -0
  32. devsper/cli/github_oauth.py +157 -0
  33. devsper/cli/init.py +637 -0
  34. devsper/cli/main.py +2956 -0
  35. devsper/cli/run_progress.py +103 -0
  36. devsper/cli/ui/__init__.py +65 -0
  37. devsper/cli/ui/components.py +94 -0
  38. devsper/cli/ui/errors.py +104 -0
  39. devsper/cli/ui/logging.py +120 -0
  40. devsper/cli/ui/onboarding.py +102 -0
  41. devsper/cli/ui/progress.py +43 -0
  42. devsper/cli/ui/run_view.py +308 -0
  43. devsper/cli/ui/theme.py +40 -0
  44. devsper/cluster/__init__.py +29 -0
  45. devsper/cluster/election.py +84 -0
  46. devsper/cluster/local.py +97 -0
  47. devsper/cluster/node_info.py +77 -0
  48. devsper/cluster/registry.py +71 -0
  49. devsper/cluster/router.py +117 -0
  50. devsper/cluster/state_backend.py +105 -0
  51. devsper/compliance/__init__.py +5 -0
  52. devsper/compliance/pii.py +147 -0
  53. devsper/config/__init__.py +52 -0
  54. devsper/config/config_loader.py +121 -0
  55. devsper/config/defaults.py +77 -0
  56. devsper/config/resolver.py +342 -0
  57. devsper/config/schema.py +237 -0
  58. devsper/credentials/__init__.py +19 -0
  59. devsper/credentials/cli.py +197 -0
  60. devsper/credentials/migration.py +124 -0
  61. devsper/credentials/store.py +142 -0
  62. devsper/dashboard/__init__.py +9 -0
  63. devsper/dashboard/dashboard.py +87 -0
  64. devsper/dev/__init__.py +25 -0
  65. devsper/dev/builder.py +195 -0
  66. devsper/dev/debugger.py +95 -0
  67. devsper/dev/repo_index.py +138 -0
  68. devsper/dev/sandbox.py +203 -0
  69. devsper/dev/scaffold.py +122 -0
  70. devsper/embeddings/__init__.py +5 -0
  71. devsper/embeddings/service.py +36 -0
  72. devsper/explainability/__init__.py +14 -0
  73. devsper/explainability/decision_tree.py +104 -0
  74. devsper/explainability/rationale.py +38 -0
  75. devsper/explainability/simulation.py +56 -0
  76. devsper/hitl/__init__.py +13 -0
  77. devsper/hitl/approval.py +160 -0
  78. devsper/hitl/escalation.py +95 -0
  79. devsper/intelligence/__init__.py +9 -0
  80. devsper/intelligence/adaptation.py +88 -0
  81. devsper/intelligence/analysis/__init__.py +19 -0
  82. devsper/intelligence/analysis/analyzer.py +71 -0
  83. devsper/intelligence/analysis/cost_estimator.py +66 -0
  84. devsper/intelligence/analysis/formatter.py +103 -0
  85. devsper/intelligence/analysis/run_report.py +402 -0
  86. devsper/intelligence/learning_engine.py +92 -0
  87. devsper/intelligence/strategies/__init__.py +23 -0
  88. devsper/intelligence/strategies/base.py +14 -0
  89. devsper/intelligence/strategies/code_analysis_strategy.py +33 -0
  90. devsper/intelligence/strategies/data_science_strategy.py +33 -0
  91. devsper/intelligence/strategies/document_pipeline_strategy.py +33 -0
  92. devsper/intelligence/strategies/experiment_strategy.py +33 -0
  93. devsper/intelligence/strategies/research_strategy.py +34 -0
  94. devsper/intelligence/strategy_selector.py +84 -0
  95. devsper/intelligence/synthesis.py +132 -0
  96. devsper/intelligence/task_optimizer.py +92 -0
  97. devsper/knowledge/__init__.py +5 -0
  98. devsper/knowledge/extractor.py +204 -0
  99. devsper/knowledge/knowledge_graph.py +184 -0
  100. devsper/knowledge/query.py +285 -0
  101. devsper/memory/__init__.py +35 -0
  102. devsper/memory/consolidation.py +138 -0
  103. devsper/memory/embeddings.py +60 -0
  104. devsper/memory/memory_index.py +97 -0
  105. devsper/memory/memory_router.py +62 -0
  106. devsper/memory/memory_store.py +221 -0
  107. devsper/memory/memory_types.py +54 -0
  108. devsper/memory/namespaces.py +45 -0
  109. devsper/memory/scoring.py +77 -0
  110. devsper/memory/summarizer.py +52 -0
  111. devsper/nodes/__init__.py +5 -0
  112. devsper/nodes/controller.py +449 -0
  113. devsper/nodes/rpc.py +127 -0
  114. devsper/nodes/single.py +161 -0
  115. devsper/nodes/worker.py +506 -0
  116. devsper/orchestration/__init__.py +19 -0
  117. devsper/orchestration/meta_planner.py +239 -0
  118. devsper/orchestration/priority_queue.py +61 -0
  119. devsper/plugins/__init__.py +19 -0
  120. devsper/plugins/marketplace/__init__.py +0 -0
  121. devsper/plugins/plugin_loader.py +70 -0
  122. devsper/plugins/plugin_registry.py +34 -0
  123. devsper/plugins/registry.py +83 -0
  124. devsper/protocols/__init__.py +6 -0
  125. devsper/providers/__init__.py +17 -0
  126. devsper/providers/anthropic.py +84 -0
  127. devsper/providers/base.py +75 -0
  128. devsper/providers/complexity_router.py +94 -0
  129. devsper/providers/gemini.py +36 -0
  130. devsper/providers/github.py +180 -0
  131. devsper/providers/model_router.py +40 -0
  132. devsper/providers/openai.py +105 -0
  133. devsper/providers/router/__init__.py +21 -0
  134. devsper/providers/router/backends/__init__.py +19 -0
  135. devsper/providers/router/backends/anthropic_backend.py +111 -0
  136. devsper/providers/router/backends/custom_backend.py +138 -0
  137. devsper/providers/router/backends/gemini_backend.py +89 -0
  138. devsper/providers/router/backends/github_backend.py +165 -0
  139. devsper/providers/router/backends/ollama_backend.py +104 -0
  140. devsper/providers/router/backends/openai_backend.py +142 -0
  141. devsper/providers/router/backends/vllm_backend.py +35 -0
  142. devsper/providers/router/base.py +60 -0
  143. devsper/providers/router/factory.py +92 -0
  144. devsper/providers/router/legacy.py +101 -0
  145. devsper/providers/router/router.py +135 -0
  146. devsper/reasoning/__init__.py +12 -0
  147. devsper/reasoning/graph.py +59 -0
  148. devsper/reasoning/nodes.py +20 -0
  149. devsper/reasoning/store.py +67 -0
  150. devsper/runtime/__init__.py +12 -0
  151. devsper/runtime/health.py +88 -0
  152. devsper/runtime/replay.py +53 -0
  153. devsper/runtime/replay_engine.py +142 -0
  154. devsper/runtime/run_history.py +204 -0
  155. devsper/runtime/telemetry.py +116 -0
  156. devsper/runtime/visualize.py +58 -0
  157. devsper/sandbox/__init__.py +13 -0
  158. devsper/sandbox/sandbox.py +161 -0
  159. devsper/swarm/checkpointer.py +65 -0
  160. devsper/swarm/executor.py +558 -0
  161. devsper/swarm/map_reduce.py +44 -0
  162. devsper/swarm/planner.py +197 -0
  163. devsper/swarm/prefetcher.py +91 -0
  164. devsper/swarm/scheduler.py +153 -0
  165. devsper/swarm/speculation.py +47 -0
  166. devsper/swarm/swarm.py +562 -0
  167. devsper/tools/__init__.py +33 -0
  168. devsper/tools/base.py +29 -0
  169. devsper/tools/code_intelligence/__init__.py +13 -0
  170. devsper/tools/code_intelligence/api_surface_extractor.py +73 -0
  171. devsper/tools/code_intelligence/architecture_analyzer.py +65 -0
  172. devsper/tools/code_intelligence/codebase_indexer.py +71 -0
  173. devsper/tools/code_intelligence/dependency_graph_builder.py +67 -0
  174. devsper/tools/code_intelligence/design_pattern_detector.py +62 -0
  175. devsper/tools/code_intelligence/large_function_detector.py +68 -0
  176. devsper/tools/code_intelligence/module_responsibility_mapper.py +56 -0
  177. devsper/tools/code_intelligence/parallel_codebase_analysis.py +44 -0
  178. devsper/tools/code_intelligence/refactor_candidate_detector.py +81 -0
  179. devsper/tools/code_intelligence/repository_semantic_index.py +61 -0
  180. devsper/tools/code_intelligence/test_coverage_estimator.py +62 -0
  181. devsper/tools/coding/__init__.py +12 -0
  182. devsper/tools/coding/analyze_code_complexity.py +48 -0
  183. devsper/tools/coding/dependency_analyzer.py +42 -0
  184. devsper/tools/coding/extract_functions.py +38 -0
  185. devsper/tools/coding/format_python.py +50 -0
  186. devsper/tools/coding/generate_docstrings.py +40 -0
  187. devsper/tools/coding/generate_unit_tests.py +42 -0
  188. devsper/tools/coding/lint_python.py +51 -0
  189. devsper/tools/coding/refactor_function.py +41 -0
  190. devsper/tools/coding/repo_structure_map.py +54 -0
  191. devsper/tools/coding/run_python.py +53 -0
  192. devsper/tools/data/__init__.py +12 -0
  193. devsper/tools/data/column_type_detection.py +64 -0
  194. devsper/tools/data/csv_summary.py +52 -0
  195. devsper/tools/data/dataframe_filter.py +51 -0
  196. devsper/tools/data/dataframe_groupby.py +47 -0
  197. devsper/tools/data/dataframe_stats.py +38 -0
  198. devsper/tools/data/dataset_sampling.py +55 -0
  199. devsper/tools/data/dataset_schema.py +45 -0
  200. devsper/tools/data/json_pretty_print.py +37 -0
  201. devsper/tools/data/json_query.py +46 -0
  202. devsper/tools/data/missing_value_report.py +47 -0
  203. devsper/tools/data_science/__init__.py +13 -0
  204. devsper/tools/data_science/correlation_heatmap.py +72 -0
  205. devsper/tools/data_science/dataset_bias_detector.py +49 -0
  206. devsper/tools/data_science/dataset_distribution_report.py +64 -0
  207. devsper/tools/data_science/dataset_drift_detector.py +64 -0
  208. devsper/tools/data_science/dataset_outlier_detector.py +65 -0
  209. devsper/tools/data_science/dataset_profile.py +76 -0
  210. devsper/tools/data_science/distributed_dataset_processor.py +54 -0
  211. devsper/tools/data_science/feature_engineering_suggestions.py +69 -0
  212. devsper/tools/data_science/feature_importance_estimator.py +82 -0
  213. devsper/tools/data_science/model_input_validator.py +59 -0
  214. devsper/tools/data_science/time_series_analyzer.py +57 -0
  215. devsper/tools/documents/__init__.py +11 -0
  216. devsper/tools/documents/_docproc.py +56 -0
  217. devsper/tools/documents/document_to_markdown.py +29 -0
  218. devsper/tools/documents/extract_document_images.py +39 -0
  219. devsper/tools/documents/extract_document_text.py +29 -0
  220. devsper/tools/documents/extract_equations.py +36 -0
  221. devsper/tools/documents/extract_tables.py +47 -0
  222. devsper/tools/documents/summarize_document.py +42 -0
  223. devsper/tools/documents/write_latex_document.py +133 -0
  224. devsper/tools/documents/write_markdown_document.py +89 -0
  225. devsper/tools/documents/write_word_document.py +149 -0
  226. devsper/tools/experiments/__init__.py +13 -0
  227. devsper/tools/experiments/bootstrap_estimator.py +54 -0
  228. devsper/tools/experiments/experiment_report_generator.py +50 -0
  229. devsper/tools/experiments/experiment_tracker.py +36 -0
  230. devsper/tools/experiments/grid_search_runner.py +50 -0
  231. devsper/tools/experiments/model_benchmark_runner.py +45 -0
  232. devsper/tools/experiments/monte_carlo_experiment.py +38 -0
  233. devsper/tools/experiments/parameter_sweep_runner.py +51 -0
  234. devsper/tools/experiments/result_comparator.py +58 -0
  235. devsper/tools/experiments/simulation_runner.py +43 -0
  236. devsper/tools/experiments/statistical_significance_test.py +56 -0
  237. devsper/tools/experiments/swarm_map_reduce.py +42 -0
  238. devsper/tools/filesystem/__init__.py +12 -0
  239. devsper/tools/filesystem/append_file.py +42 -0
  240. devsper/tools/filesystem/file_hash.py +40 -0
  241. devsper/tools/filesystem/file_line_count.py +36 -0
  242. devsper/tools/filesystem/file_metadata.py +38 -0
  243. devsper/tools/filesystem/file_preview.py +55 -0
  244. devsper/tools/filesystem/find_large_files.py +50 -0
  245. devsper/tools/filesystem/list_directory.py +39 -0
  246. devsper/tools/filesystem/read_file.py +35 -0
  247. devsper/tools/filesystem/search_files.py +60 -0
  248. devsper/tools/filesystem/write_file.py +41 -0
  249. devsper/tools/flagship/__init__.py +15 -0
  250. devsper/tools/flagship/distributed_document_analysis.py +77 -0
  251. devsper/tools/flagship/docproc_corpus_pipeline.py +91 -0
  252. devsper/tools/flagship/repository_semantic_map.py +99 -0
  253. devsper/tools/flagship/research_graph_builder.py +111 -0
  254. devsper/tools/flagship/swarm_experiment_runner.py +86 -0
  255. devsper/tools/knowledge/__init__.py +10 -0
  256. devsper/tools/knowledge/citation_graph_builder.py +69 -0
  257. devsper/tools/knowledge/concept_frequency_analyzer.py +74 -0
  258. devsper/tools/knowledge/corpus_builder.py +66 -0
  259. devsper/tools/knowledge/cross_document_entity_linker.py +71 -0
  260. devsper/tools/knowledge/document_corpus_summary.py +68 -0
  261. devsper/tools/knowledge/document_topic_extractor.py +58 -0
  262. devsper/tools/knowledge/knowledge_graph_extractor.py +58 -0
  263. devsper/tools/knowledge/timeline_extractor.py +59 -0
  264. devsper/tools/math/__init__.py +12 -0
  265. devsper/tools/math/calculate_expression.py +52 -0
  266. devsper/tools/math/correlation.py +44 -0
  267. devsper/tools/math/distribution_summary.py +39 -0
  268. devsper/tools/math/histogram.py +53 -0
  269. devsper/tools/math/linear_regression.py +47 -0
  270. devsper/tools/math/matrix_multiply.py +38 -0
  271. devsper/tools/math/mean_std.py +35 -0
  272. devsper/tools/math/monte_carlo_simulation.py +43 -0
  273. devsper/tools/math/polynomial_fit.py +40 -0
  274. devsper/tools/math/random_sample.py +36 -0
  275. devsper/tools/mcp/__init__.py +23 -0
  276. devsper/tools/mcp/adapter.py +53 -0
  277. devsper/tools/mcp/client.py +235 -0
  278. devsper/tools/mcp/discovery.py +53 -0
  279. devsper/tools/memory/__init__.py +16 -0
  280. devsper/tools/memory/delete_memory.py +25 -0
  281. devsper/tools/memory/list_memory.py +34 -0
  282. devsper/tools/memory/search_memory.py +36 -0
  283. devsper/tools/memory/store_memory.py +47 -0
  284. devsper/tools/memory/summarize_memory.py +41 -0
  285. devsper/tools/memory/tag_memory.py +47 -0
  286. devsper/tools/pipelines.py +92 -0
  287. devsper/tools/registry.py +39 -0
  288. devsper/tools/research/__init__.py +12 -0
  289. devsper/tools/research/arxiv_download.py +55 -0
  290. devsper/tools/research/arxiv_search.py +58 -0
  291. devsper/tools/research/citation_extractor.py +35 -0
  292. devsper/tools/research/duckduckgo_search.py +42 -0
  293. devsper/tools/research/paper_metadata_extractor.py +45 -0
  294. devsper/tools/research/paper_summarizer.py +41 -0
  295. devsper/tools/research/research_question_generator.py +39 -0
  296. devsper/tools/research/topic_cluster.py +46 -0
  297. devsper/tools/research/web_search.py +47 -0
  298. devsper/tools/research/wikipedia_lookup.py +50 -0
  299. devsper/tools/research_advanced/__init__.py +14 -0
  300. devsper/tools/research_advanced/citation_context_extractor.py +60 -0
  301. devsper/tools/research_advanced/literature_review_generator.py +79 -0
  302. devsper/tools/research_advanced/methodology_extractor.py +58 -0
  303. devsper/tools/research_advanced/paper_contribution_extractor.py +50 -0
  304. devsper/tools/research_advanced/paper_dataset_identifier.py +49 -0
  305. devsper/tools/research_advanced/paper_method_comparator.py +62 -0
  306. devsper/tools/research_advanced/paper_similarity_search.py +69 -0
  307. devsper/tools/research_advanced/paper_trend_analyzer.py +69 -0
  308. devsper/tools/research_advanced/parallel_document_analyzer.py +56 -0
  309. devsper/tools/research_advanced/research_gap_finder.py +71 -0
  310. devsper/tools/research_advanced/research_topic_mapper.py +69 -0
  311. devsper/tools/research_advanced/swarm_literature_review.py +58 -0
  312. devsper/tools/scoring/__init__.py +52 -0
  313. devsper/tools/scoring/report.py +44 -0
  314. devsper/tools/scoring/scorer.py +39 -0
  315. devsper/tools/scoring/selector.py +61 -0
  316. devsper/tools/scoring/store.py +267 -0
  317. devsper/tools/selector.py +130 -0
  318. devsper/tools/system/__init__.py +12 -0
  319. devsper/tools/system/cpu_usage.py +22 -0
  320. devsper/tools/system/disk_usage.py +35 -0
  321. devsper/tools/system/environment_variables.py +29 -0
  322. devsper/tools/system/memory_usage.py +23 -0
  323. devsper/tools/system/pip_install.py +44 -0
  324. devsper/tools/system/pip_search.py +29 -0
  325. devsper/tools/system/process_list.py +34 -0
  326. devsper/tools/system/python_package_list.py +40 -0
  327. devsper/tools/system/run_shell_command.py +51 -0
  328. devsper/tools/system/system_info.py +26 -0
  329. devsper/tools/tool_runner.py +122 -0
  330. devsper/tui/__init__.py +5 -0
  331. devsper/tui/activity_feed_view.py +73 -0
  332. devsper/tui/adaptive_tasks_view.py +75 -0
  333. devsper/tui/agent_role_view.py +35 -0
  334. devsper/tui/app.py +395 -0
  335. devsper/tui/dashboard_screen.py +290 -0
  336. devsper/tui/dev_view.py +99 -0
  337. devsper/tui/inject_screen.py +73 -0
  338. devsper/tui/knowledge_graph_view.py +46 -0
  339. devsper/tui/layout.py +43 -0
  340. devsper/tui/logs_view.py +83 -0
  341. devsper/tui/memory_view.py +58 -0
  342. devsper/tui/performance_view.py +33 -0
  343. devsper/tui/reasoning_graph_view.py +39 -0
  344. devsper/tui/results_view.py +139 -0
  345. devsper/tui/swarm_view.py +37 -0
  346. devsper/tui/task_detail_screen.py +55 -0
  347. devsper/tui/task_view.py +103 -0
  348. devsper/types/event.py +97 -0
  349. devsper/types/exceptions.py +21 -0
  350. devsper/types/swarm.py +41 -0
  351. devsper/types/task.py +80 -0
  352. devsper/upgrade/__init__.py +21 -0
  353. devsper/upgrade/changelog.py +124 -0
  354. devsper/upgrade/cli.py +145 -0
  355. devsper/upgrade/installer.py +103 -0
  356. devsper/upgrade/notifier.py +52 -0
  357. devsper/upgrade/version_check.py +121 -0
  358. devsper/utils/event_logger.py +88 -0
  359. devsper/utils/http.py +43 -0
  360. devsper/utils/models.py +54 -0
  361. devsper/visualization/__init__.py +5 -0
  362. devsper/visualization/dag_export.py +67 -0
  363. devsper/workflow/__init__.py +18 -0
  364. devsper/workflow/conditions.py +157 -0
  365. devsper/workflow/context.py +108 -0
  366. devsper/workflow/loader.py +156 -0
  367. devsper/workflow/resolver.py +109 -0
  368. devsper/workflow/runner.py +562 -0
  369. devsper/workflow/schema.py +63 -0
  370. devsper/workflow/validator.py +128 -0
  371. devsper-2.1.6.dist-info/METADATA +346 -0
  372. devsper-2.1.6.dist-info/RECORD +375 -0
  373. devsper-2.1.6.dist-info/WHEEL +4 -0
  374. devsper-2.1.6.dist-info/entry_points.txt +3 -0
  375. devsper-2.1.6.dist-info/licenses/LICENSE +639 -0
@@ -0,0 +1,267 @@
1
+ """
2
+ Persistent score store for tool reliability (SQLite at ~/.config/devsper/tool_scores.db).
3
+ """
4
+
5
+ import sqlite3
6
+ from dataclasses import dataclass
7
+ from datetime import datetime, timezone, timedelta
8
+ from pathlib import Path
9
+
10
+
11
+ def _default_db_path() -> Path:
12
+ p = Path.home() / ".config" / "devsper" / "tool_scores.db"
13
+ p.parent.mkdir(parents=True, exist_ok=True)
14
+ return p
15
+
16
+
17
+ @dataclass
18
+ class ToolScore:
19
+ tool_name: str
20
+ success_rate: float # 0.0–1.0
21
+ avg_latency_ms: float
22
+ p95_latency_ms: float
23
+ total_calls: int
24
+ recent_failures: int
25
+ composite_score: float # final blended score
26
+ last_updated: str
27
+ is_new: bool # True if total_calls < 5 (no penalty for new tools)
28
+
29
+
30
+ class ToolScoreStore:
31
+ """SQLite-backed store for tool results and materialized tool_scores."""
32
+
33
+ def __init__(self, db_path: str | Path | None = None) -> None:
34
+ self.db_path = Path(db_path) if db_path else _default_db_path()
35
+ self.db_path.parent.mkdir(parents=True, exist_ok=True)
36
+ self._init_schema()
37
+
38
+ def _conn(self) -> sqlite3.Connection:
39
+ return sqlite3.connect(str(self.db_path))
40
+
41
+ def _init_schema(self) -> None:
42
+ with self._conn() as c:
43
+ c.execute("""
44
+ CREATE TABLE IF NOT EXISTS tool_results (
45
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
46
+ tool_name TEXT NOT NULL,
47
+ task_type TEXT,
48
+ success INTEGER NOT NULL,
49
+ latency_ms INTEGER,
50
+ error_type TEXT,
51
+ timestamp TEXT NOT NULL
52
+ )
53
+ """)
54
+ c.execute("""
55
+ CREATE TABLE IF NOT EXISTS tool_scores (
56
+ tool_name TEXT PRIMARY KEY,
57
+ success_rate REAL,
58
+ avg_latency_ms REAL,
59
+ p95_latency_ms REAL,
60
+ total_calls INTEGER,
61
+ recent_failures INTEGER,
62
+ composite_score REAL,
63
+ last_updated TEXT
64
+ )
65
+ """)
66
+ c.execute(
67
+ "CREATE INDEX IF NOT EXISTS idx_tool_results_name ON tool_results(tool_name)"
68
+ )
69
+ c.execute(
70
+ "CREATE INDEX IF NOT EXISTS idx_tool_results_ts ON tool_results(timestamp)"
71
+ )
72
+
73
+ def record(
74
+ self,
75
+ tool_name: str,
76
+ task_type: str | None,
77
+ success: bool,
78
+ latency_ms: int | None = None,
79
+ error_type: str | None = None,
80
+ ) -> None:
81
+ """Insert one result and recompute/upsert tool_scores for this tool."""
82
+ ts = datetime.now(timezone.utc).isoformat()
83
+ with self._conn() as c:
84
+ c.execute(
85
+ """INSERT INTO tool_results
86
+ (tool_name, task_type, success, latency_ms, error_type, timestamp)
87
+ VALUES (?, ?, ?, ?, ?, ?)""",
88
+ (
89
+ tool_name,
90
+ task_type or "general",
91
+ 1 if success else 0,
92
+ latency_ms,
93
+ error_type,
94
+ ts,
95
+ ),
96
+ )
97
+ self._recompute_scores(tool_name)
98
+
99
+ def _recompute_scores(self, tool_name: str) -> None:
100
+ from devsper.tools.scoring.scorer import compute_composite_score
101
+
102
+ with self._conn() as c:
103
+ rows = c.execute(
104
+ """SELECT success, latency_ms
105
+ FROM tool_results
106
+ WHERE tool_name = ?
107
+ ORDER BY id DESC""",
108
+ (tool_name,),
109
+ ).fetchall()
110
+ if not rows:
111
+ return
112
+ total = len(rows)
113
+ successes = sum(1 for r in rows if r[0] == 1)
114
+ success_rate = successes / total if total else 0.0
115
+ latencies = [r[1] for r in rows if r[1] is not None]
116
+ avg_latency_ms = sum(latencies) / len(latencies) if latencies else 0.0
117
+ if latencies:
118
+ sorted_lat = sorted(latencies)
119
+ idx = max(0, int(len(sorted_lat) * 0.95) - 1)
120
+ p95_latency_ms = sorted_lat[idx]
121
+ else:
122
+ p95_latency_ms = 0.0
123
+ recent_20 = rows[:20]
124
+ recent_failures = sum(1 for r in recent_20 if r[0] == 0)
125
+ stats = {
126
+ "success_rate": success_rate,
127
+ "avg_latency_ms": avg_latency_ms,
128
+ "p95_latency_ms": p95_latency_ms,
129
+ "total_calls": total,
130
+ "recent_failures": recent_failures,
131
+ }
132
+ composite = compute_composite_score(stats)
133
+ with self._conn() as c2:
134
+ r = c2.execute(
135
+ "SELECT timestamp FROM tool_results WHERE tool_name = ? ORDER BY id DESC LIMIT 1",
136
+ (tool_name,),
137
+ ).fetchone()
138
+ last_ts = r[0] if r else datetime.now(timezone.utc).isoformat()
139
+ with self._conn() as c:
140
+ c.execute(
141
+ """INSERT INTO tool_scores
142
+ (tool_name, success_rate, avg_latency_ms, p95_latency_ms,
143
+ total_calls, recent_failures, composite_score, last_updated)
144
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
145
+ ON CONFLICT(tool_name) DO UPDATE SET
146
+ success_rate = excluded.success_rate,
147
+ avg_latency_ms = excluded.avg_latency_ms,
148
+ p95_latency_ms = excluded.p95_latency_ms,
149
+ total_calls = excluded.total_calls,
150
+ recent_failures = excluded.recent_failures,
151
+ composite_score = excluded.composite_score,
152
+ last_updated = excluded.last_updated""",
153
+ (
154
+ tool_name,
155
+ success_rate,
156
+ avg_latency_ms,
157
+ p95_latency_ms,
158
+ total,
159
+ recent_failures,
160
+ composite,
161
+ last_ts,
162
+ ),
163
+ )
164
+
165
+ def get_score(self, tool_name: str) -> ToolScore | None:
166
+ row = None
167
+ with self._conn() as c:
168
+ row = c.execute(
169
+ "SELECT tool_name, success_rate, avg_latency_ms, p95_latency_ms,"
170
+ " total_calls, recent_failures, composite_score, last_updated"
171
+ " FROM tool_scores WHERE tool_name = ?",
172
+ (tool_name,),
173
+ ).fetchone()
174
+ if not row:
175
+ return None
176
+ return ToolScore(
177
+ tool_name=row[0],
178
+ success_rate=row[1],
179
+ avg_latency_ms=row[2],
180
+ p95_latency_ms=row[3],
181
+ total_calls=row[4],
182
+ recent_failures=row[5],
183
+ composite_score=row[6],
184
+ last_updated=row[7],
185
+ is_new=row[4] < 5,
186
+ )
187
+
188
+ def get_all_scores(self) -> list[ToolScore]:
189
+ with self._conn() as c:
190
+ rows = c.execute(
191
+ """SELECT tool_name, success_rate, avg_latency_ms, p95_latency_ms,
192
+ total_calls, recent_failures, composite_score, last_updated
193
+ FROM tool_scores
194
+ ORDER BY composite_score DESC"""
195
+ ).fetchall()
196
+ return [
197
+ ToolScore(
198
+ tool_name=r[0],
199
+ success_rate=r[1],
200
+ avg_latency_ms=r[2],
201
+ p95_latency_ms=r[3],
202
+ total_calls=r[4],
203
+ recent_failures=r[5],
204
+ composite_score=r[6],
205
+ last_updated=r[7],
206
+ is_new=r[4] < 5,
207
+ )
208
+ for r in rows
209
+ ]
210
+
211
+ def get_scores_for_tools(self, tool_names: list[str]) -> dict[str, ToolScore]:
212
+ result: dict[str, ToolScore] = {}
213
+ for name in tool_names:
214
+ s = self.get_score(name)
215
+ if s is not None:
216
+ result[name] = s
217
+ return result
218
+
219
+ def reset(self, tool_name: str | None) -> None:
220
+ """Delete all records for a tool (or all tools if tool_name is None)."""
221
+ with self._conn() as c:
222
+ if tool_name is None:
223
+ c.execute("DELETE FROM tool_results")
224
+ c.execute("DELETE FROM tool_scores")
225
+ else:
226
+ c.execute("DELETE FROM tool_results WHERE tool_name = ?", (tool_name,))
227
+ c.execute("DELETE FROM tool_scores WHERE tool_name = ?", (tool_name,))
228
+
229
+ def prune(self, days: int = 90) -> int:
230
+ """Delete results older than N days. Returns number of rows deleted."""
231
+ cutoff = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat()
232
+ with self._conn() as c:
233
+ cur = c.execute("DELETE FROM tool_results WHERE timestamp < ?", (cutoff,))
234
+ deleted = cur.rowcount
235
+ # Recompute scores for tools that still have results; drop scores for tools with no results left
236
+ with self._conn() as c:
237
+ remaining = {
238
+ r[0] for r in c.execute("SELECT DISTINCT tool_name FROM tool_results").fetchall()
239
+ }
240
+ all_scored = [
241
+ r[0] for r in c.execute("SELECT tool_name FROM tool_scores").fetchall()
242
+ ]
243
+ for name in all_scored:
244
+ if name in remaining:
245
+ self._recompute_scores(name)
246
+ else:
247
+ with self._conn() as c:
248
+ c.execute("DELETE FROM tool_scores WHERE tool_name = ?", (name,))
249
+ return deleted
250
+
251
+ def result_count(self) -> int:
252
+ """Total number of result rows (for doctor)."""
253
+ with self._conn() as c:
254
+ r = c.execute("SELECT COUNT(*) FROM tool_results").fetchone()
255
+ return r[0] if r else 0
256
+
257
+ def tool_count(self) -> int:
258
+ """Number of tools with scores (for doctor)."""
259
+ with self._conn() as c:
260
+ r = c.execute("SELECT COUNT(*) FROM tool_scores").fetchone()
261
+ return r[0] if r else 0
262
+
263
+ def get_cached_tool_names(self) -> list[str]:
264
+ """Tool names with scores (warm cache for distributed routing)."""
265
+ with self._conn() as c:
266
+ rows = c.execute("SELECT tool_name FROM tool_scores ORDER BY composite_score DESC").fetchall()
267
+ return [r[0] for r in rows]
@@ -0,0 +1,130 @@
1
+ """
2
+ Smart tool selection: filter by category and select top_k tools by semantic similarity to the task.
3
+ """
4
+
5
+ import os
6
+
7
+ from devsper.memory.embeddings import embed_text
8
+ from devsper.tools.base import Tool
9
+ from devsper.tools.registry import list_tools
10
+
11
+
12
+ def _tool_category(tool: Tool) -> str:
13
+ """Infer category from tool.category or from module path (e.g. devsper.tools.research -> research)."""
14
+ if getattr(tool, "category", "") and str(tool.category).strip():
15
+ return str(tool.category).strip().lower()
16
+ module = getattr(tool.__class__, "__module__", "") or ""
17
+ if "devsper.tools." in module:
18
+ parts = module.split(".")
19
+ for i, p in enumerate(parts):
20
+ if p == "tools" and i + 1 < len(parts):
21
+ return parts[i + 1].lower()
22
+ return "general"
23
+
24
+
25
+ def _cosine_similarity(a: list[float], b: list[float]) -> float:
26
+ if not a or not b or len(a) != len(b):
27
+ return 0.0
28
+ dot = sum(x * y for x, y in zip(a, b))
29
+ na = sum(x * x for x in a) ** 0.5
30
+ nb = sum(x * x for x in b) ** 0.5
31
+ if na == 0 or nb == 0:
32
+ return 0.0
33
+ return dot / (na * nb)
34
+
35
+
36
+ def _top_k_by_similarity(
37
+ task_description: str,
38
+ tools: list[Tool],
39
+ top_k: int,
40
+ ) -> list[Tool]:
41
+ """Return top_k tools from the list by semantic similarity to task_description."""
42
+ if not tools or top_k <= 0:
43
+ return tools
44
+ task_embedding = embed_text(task_description or " ")
45
+ tool_texts = [f"{t.name}: {t.description}" for t in tools]
46
+ tool_embeddings = [embed_text(t) for t in tool_texts]
47
+ scored = [
48
+ (t, _cosine_similarity(task_embedding, te))
49
+ for t, te in zip(tools, tool_embeddings)
50
+ ]
51
+ scored.sort(key=lambda x: -x[1])
52
+ return [t for t, _ in scored[:top_k]]
53
+
54
+
55
+ def select_tools_for_task(
56
+ task_description: str,
57
+ top_k: int = 12,
58
+ enabled_categories: list[str] | None = None,
59
+ ) -> list[Tool]:
60
+ """
61
+ Return tools most relevant to the task: filter by enabled_categories (if set),
62
+ then by semantic similarity, return top_k. If top_k <= 0, return all (no limit).
63
+ """
64
+ all_tools = list_tools()
65
+ if enabled_categories is not None and len(enabled_categories) > 0:
66
+ allowed = {c.lower().strip() for c in enabled_categories}
67
+ all_tools = [t for t in all_tools if _tool_category(t) in allowed]
68
+ if not all_tools:
69
+ return []
70
+ if top_k <= 0:
71
+ return all_tools
72
+ return _top_k_by_similarity(task_description, all_tools, top_k)
73
+
74
+
75
+ def get_tools_for_task(
76
+ task_description: str,
77
+ config: object | None = None,
78
+ role: str | None = None,
79
+ score_store: object | None = None,
80
+ ) -> list[Tool]:
81
+ """
82
+ Return tools for the agent: use selector when config has tools.top_k > 0,
83
+ otherwise return all tools. If role is set, filter by role's tool_categories.
84
+ When score_store is provided and DEVSPER_DISABLE_TOOL_SCORING is not set,
85
+ uses blended similarity + reliability ranking.
86
+ """
87
+ if config is None:
88
+ try:
89
+ from devsper.config import get_config
90
+ config = get_config()
91
+ except Exception:
92
+ config = None
93
+
94
+ tools_config = getattr(config, "tools", None) if config else None
95
+ top_k = getattr(tools_config, "top_k", 0) if tools_config else 0
96
+ enabled = getattr(tools_config, "enabled", None) if tools_config else None
97
+
98
+ if role:
99
+ from devsper.agents.roles import get_role_config
100
+ role_config = get_role_config(role)
101
+ if role_config.tool_categories:
102
+ enabled = role_config.tool_categories
103
+
104
+ all_tools = list_tools()
105
+ if enabled is not None and len(enabled) > 0:
106
+ allowed = {c.lower().strip() for c in enabled}
107
+ all_tools = [t for t in all_tools if _tool_category(t) in allowed]
108
+
109
+ if not all_tools:
110
+ return []
111
+ if top_k <= 0:
112
+ return all_tools
113
+
114
+ top_k_val = top_k
115
+ if top_k_val <= 0:
116
+ return all_tools
117
+
118
+ use_scoring = (
119
+ score_store is not None
120
+ and os.environ.get("DEVSPER_DISABLE_TOOL_SCORING", "").strip() != "1"
121
+ )
122
+ if use_scoring:
123
+ from devsper.tools.scoring.selector import select_tools_scored
124
+ return select_tools_scored(
125
+ task_description or "",
126
+ all_tools,
127
+ top_k_val,
128
+ score_store,
129
+ )
130
+ return _top_k_by_similarity(task_description or "", all_tools, top_k_val)
@@ -0,0 +1,12 @@
1
+ """System tools: shell, info, CPU, memory, disk, processes, env, pip."""
2
+
3
+ from devsper.tools.system.run_shell_command import RunShellCommandTool
4
+ from devsper.tools.system.system_info import SystemInfoTool
5
+ from devsper.tools.system.cpu_usage import CpuUsageTool
6
+ from devsper.tools.system.memory_usage import MemoryUsageTool
7
+ from devsper.tools.system.disk_usage import DiskUsageTool
8
+ from devsper.tools.system.process_list import ProcessListTool
9
+ from devsper.tools.system.environment_variables import EnvironmentVariablesTool
10
+ from devsper.tools.system.python_package_list import PythonPackageListTool
11
+ from devsper.tools.system.pip_install import PipInstallTool
12
+ from devsper.tools.system.pip_search import PipSearchTool
@@ -0,0 +1,22 @@
1
+ """Report CPU usage (basic). Uses psutil if available, else placeholder."""
2
+
3
+ from devsper.tools.base import Tool
4
+ from devsper.tools.registry import register
5
+
6
+
7
+ class CpuUsageTool(Tool):
8
+ """Return CPU usage percent. Uses psutil if installed."""
9
+
10
+ name = "cpu_usage"
11
+ description = "Get CPU usage percentage. Requires psutil or returns N/A."
12
+ input_schema = {"type": "object", "properties": {}, "required": []}
13
+
14
+ def run(self, **kwargs) -> str:
15
+ try:
16
+ import psutil
17
+ return f"CPU percent: {psutil.cpu_percent(interval=1)}%"
18
+ except ImportError:
19
+ return "psutil not installed. Install with: pip install psutil"
20
+
21
+
22
+ register(CpuUsageTool())
@@ -0,0 +1,35 @@
1
+ """Report disk usage for a path. Uses stdlib shutil."""
2
+
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ from devsper.tools.base import Tool
7
+ from devsper.tools.registry import register
8
+
9
+
10
+ class DiskUsageTool(Tool):
11
+ """Return disk usage for a path: total, used, free (in bytes)."""
12
+
13
+ name = "disk_usage"
14
+ description = "Get disk usage for a path. Returns total, used, free in bytes."
15
+ input_schema = {
16
+ "type": "object",
17
+ "properties": {"path": {"type": "string", "description": "Path (default: current dir)"}},
18
+ "required": [],
19
+ }
20
+
21
+ def run(self, **kwargs) -> str:
22
+ path = kwargs.get("path", ".")
23
+ if path is not None and not isinstance(path, str):
24
+ return "Error: path must be a string"
25
+ p = Path(path or ".").resolve()
26
+ if not p.exists():
27
+ return f"Error: path not found: {p}"
28
+ try:
29
+ usage = shutil.disk_usage(p)
30
+ return f"total = {usage.total} bytes\nused = {usage.used} bytes\nfree = {usage.free} bytes"
31
+ except Exception as e:
32
+ return f"Error: {e}"
33
+
34
+
35
+ register(DiskUsageTool())
@@ -0,0 +1,29 @@
1
+ """List environment variable names (and optionally values)."""
2
+
3
+ import os
4
+
5
+ from devsper.tools.base import Tool
6
+ from devsper.tools.registry import register
7
+
8
+
9
+ class EnvironmentVariablesTool(Tool):
10
+ """List environment variable names. Optionally include values (may be sensitive)."""
11
+
12
+ name = "environment_variables"
13
+ description = "List environment variable names. Optionally show values."
14
+ input_schema = {
15
+ "type": "object",
16
+ "properties": {"show_values": {"type": "boolean", "description": "If true, include values"}},
17
+ "required": [],
18
+ }
19
+
20
+ def run(self, **kwargs) -> str:
21
+ show = kwargs.get("show_values", False)
22
+ names = sorted(os.environ.keys())
23
+ if not show:
24
+ return "\n".join(names)
25
+ lines = [f"{k}={os.environ[k]}" for k in names]
26
+ return "\n".join(lines)
27
+
28
+
29
+ register(EnvironmentVariablesTool())
@@ -0,0 +1,23 @@
1
+ """Report memory usage. Uses psutil if available."""
2
+
3
+ from devsper.tools.base import Tool
4
+ from devsper.tools.registry import register
5
+
6
+
7
+ class MemoryUsageTool(Tool):
8
+ """Return memory usage (total, used, percent). Uses psutil if installed."""
9
+
10
+ name = "memory_usage"
11
+ description = "Get memory usage. Requires psutil or returns N/A."
12
+ input_schema = {"type": "object", "properties": {}, "required": []}
13
+
14
+ def run(self, **kwargs) -> str:
15
+ try:
16
+ import psutil
17
+ v = psutil.virtual_memory()
18
+ return f"total = {v.total} bytes\nused = {v.used} bytes\npercent = {v.percent}%"
19
+ except ImportError:
20
+ return "psutil not installed. Install with: pip install psutil"
21
+
22
+
23
+ register(MemoryUsageTool())
@@ -0,0 +1,44 @@
1
+ """Install a package with pip."""
2
+
3
+ import subprocess
4
+
5
+ from devsper.tools.base import Tool
6
+ from devsper.tools.registry import register
7
+
8
+
9
+ class PipInstallTool(Tool):
10
+ """Install a package using pip. Returns pip output."""
11
+
12
+ name = "pip_install"
13
+ description = "Install a package with pip (e.g. pip install <package>)."
14
+ input_schema = {
15
+ "type": "object",
16
+ "properties": {"package": {"type": "string", "description": "Package name to install"}},
17
+ "required": ["package"],
18
+ }
19
+
20
+ def run(self, **kwargs) -> str:
21
+ package = kwargs.get("package")
22
+ if not package or not isinstance(package, str):
23
+ return "Error: package must be a non-empty string"
24
+ try:
25
+ result = subprocess.run(
26
+ ["pip", "install", package],
27
+ capture_output=True,
28
+ text=True,
29
+ timeout=120,
30
+ )
31
+ out = result.stdout or ""
32
+ err = result.stderr or ""
33
+ if err:
34
+ out = out + "\n" + err
35
+ if result.returncode != 0:
36
+ out = f"[exit {result.returncode}]\n" + out
37
+ return out.strip() or "Done"
38
+ except subprocess.TimeoutExpired:
39
+ return "Error: pip install timed out"
40
+ except Exception as e:
41
+ return f"Error: {e}"
42
+
43
+
44
+ register(PipInstallTool())
@@ -0,0 +1,29 @@
1
+ """Search PyPI (pip search is deprecated; use simple message or run pip index)."""
2
+
3
+ from devsper.tools.base import Tool
4
+ from devsper.tools.registry import register
5
+
6
+
7
+ class PipSearchTool(Tool):
8
+ """Search PyPI. pip search is disabled; suggests using web or pip index versions."""
9
+
10
+ name = "pip_search"
11
+ description = "Search PyPI. Note: pip search is disabled; returns instructions."
12
+ input_schema = {
13
+ "type": "object",
14
+ "properties": {"query": {"type": "string", "description": "Search query"}},
15
+ "required": ["query"],
16
+ }
17
+
18
+ def run(self, **kwargs) -> str:
19
+ query = kwargs.get("query")
20
+ if not query or not isinstance(query, str):
21
+ return "Error: query must be a non-empty string"
22
+ return (
23
+ "pip search is disabled on PyPI. To find packages:\n"
24
+ "1. Visit https://pypi.org/search/?q=" + query.replace(" ", "+") + "\n"
25
+ "2. Or run: pip index versions <package> for a known package name"
26
+ )
27
+
28
+
29
+ register(PipSearchTool())
@@ -0,0 +1,34 @@
1
+ """List running processes. Uses psutil if available."""
2
+
3
+ from devsper.tools.base import Tool
4
+ from devsper.tools.registry import register
5
+
6
+
7
+ class ProcessListTool(Tool):
8
+ """List running processes (pid, name). Uses psutil; limited to first N."""
9
+
10
+ name = "process_list"
11
+ description = "List running processes. Requires psutil. Returns pid and name for first N."
12
+ input_schema = {
13
+ "type": "object",
14
+ "properties": {"limit": {"type": "integer", "description": "Max processes to list (default 20)"}},
15
+ "required": [],
16
+ }
17
+
18
+ def run(self, **kwargs) -> str:
19
+ try:
20
+ import psutil
21
+ except ImportError:
22
+ return "psutil not installed. Install with: pip install psutil"
23
+ limit = kwargs.get("limit", 20)
24
+ if not isinstance(limit, int) or limit < 1:
25
+ limit = 20
26
+ try:
27
+ procs = list(psutil.process_iter(["pid", "name"]))[:limit]
28
+ lines = [f"{p.info.get('pid')} {p.info.get('name', '')}" for p in procs]
29
+ return "\n".join(lines)
30
+ except Exception as e:
31
+ return f"Error: {e}"
32
+
33
+
34
+ register(ProcessListTool())
@@ -0,0 +1,40 @@
1
+ """List installed Python packages (pip list)."""
2
+
3
+ import subprocess
4
+
5
+ from devsper.tools.base import Tool
6
+ from devsper.tools.registry import register
7
+
8
+
9
+ class PythonPackageListTool(Tool):
10
+ """Run pip list and return output."""
11
+
12
+ name = "python_package_list"
13
+ description = "List installed Python packages (pip list)."
14
+ input_schema = {
15
+ "type": "object",
16
+ "properties": {"limit": {"type": "integer", "description": "Max lines to return (default 100)"}},
17
+ "required": [],
18
+ }
19
+
20
+ def run(self, **kwargs) -> str:
21
+ limit = kwargs.get("limit", 100)
22
+ if not isinstance(limit, int) or limit < 1:
23
+ limit = 100
24
+ try:
25
+ result = subprocess.run(
26
+ ["pip", "list"],
27
+ capture_output=True,
28
+ text=True,
29
+ timeout=30,
30
+ )
31
+ out = result.stdout or result.stderr or ""
32
+ lines = out.strip().splitlines()
33
+ return "\n".join(lines[:limit])
34
+ except FileNotFoundError:
35
+ return "pip not found"
36
+ except Exception as e:
37
+ return f"Error: {e}"
38
+
39
+
40
+ register(PythonPackageListTool())