igel-qe-core 1.0.0

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 (394) hide show
  1. package/dist/cli/index.js +38 -0
  2. package/dist/mcp/server.js +104 -0
  3. package/knowledge_base/__init__.py +3 -0
  4. package/knowledge_base/__pycache__/__init__.cpython-314.pyc +0 -0
  5. package/knowledge_base/__pycache__/__init__.cpython-37.pyc +0 -0
  6. package/knowledge_base/__pycache__/__init__.cpython-38.pyc +0 -0
  7. package/knowledge_base/__pycache__/confidence.cpython-37.pyc +0 -0
  8. package/knowledge_base/__pycache__/config.cpython-314.pyc +0 -0
  9. package/knowledge_base/__pycache__/config.cpython-37.pyc +0 -0
  10. package/knowledge_base/__pycache__/config.cpython-38.pyc +0 -0
  11. package/knowledge_base/__pycache__/context_fusion.cpython-314.pyc +0 -0
  12. package/knowledge_base/__pycache__/context_fusion.cpython-37.pyc +0 -0
  13. package/knowledge_base/__pycache__/feedback_analytics.cpython-37.pyc +0 -0
  14. package/knowledge_base/__pycache__/feedback_applier.cpython-37.pyc +0 -0
  15. package/knowledge_base/__pycache__/feedback_cli.cpython-37.pyc +0 -0
  16. package/knowledge_base/__pycache__/feedback_curator.cpython-37.pyc +0 -0
  17. package/knowledge_base/__pycache__/feedback_handler.cpython-37.pyc +0 -0
  18. package/knowledge_base/__pycache__/generate.cpython-37.pyc +0 -0
  19. package/knowledge_base/__pycache__/generate_qcapps134.cpython-37.pyc +0 -0
  20. package/knowledge_base/__pycache__/input_classifier.cpython-314.pyc +0 -0
  21. package/knowledge_base/__pycache__/input_classifier.cpython-37.pyc +0 -0
  22. package/knowledge_base/__pycache__/kg_extractor.cpython-37.pyc +0 -0
  23. package/knowledge_base/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
  24. package/knowledge_base/__pycache__/knowledge_graph.cpython-37.pyc +0 -0
  25. package/knowledge_base/__pycache__/manual_tc_parser.cpython-314.pyc +0 -0
  26. package/knowledge_base/__pycache__/manual_tc_parser.cpython-37.pyc +0 -0
  27. package/knowledge_base/__pycache__/precedence.cpython-37.pyc +0 -0
  28. package/knowledge_base/__pycache__/prompts.cpython-314.pyc +0 -0
  29. package/knowledge_base/__pycache__/prompts.cpython-37.pyc +0 -0
  30. package/knowledge_base/__pycache__/reranker.cpython-314.pyc +0 -0
  31. package/knowledge_base/__pycache__/reranker.cpython-37.pyc +0 -0
  32. package/knowledge_base/__pycache__/retrieval_debug.cpython-314.pyc +0 -0
  33. package/knowledge_base/__pycache__/retrieval_debug.cpython-37.pyc +0 -0
  34. package/knowledge_base/__pycache__/retriever.cpython-314.pyc +0 -0
  35. package/knowledge_base/__pycache__/retriever.cpython-37.pyc +0 -0
  36. package/knowledge_base/__pycache__/server.cpython-37.pyc +0 -0
  37. package/knowledge_base/__pycache__/tc_transformer.cpython-314.pyc +0 -0
  38. package/knowledge_base/__pycache__/tc_transformer.cpython-37.pyc +0 -0
  39. package/knowledge_base/__pycache__/test_generator.cpython-314.pyc +0 -0
  40. package/knowledge_base/__pycache__/test_generator.cpython-37.pyc +0 -0
  41. package/knowledge_base/agents/__init__.py +6 -0
  42. package/knowledge_base/agents/__pycache__/__init__.cpython-37.pyc +0 -0
  43. package/knowledge_base/agents/__pycache__/__init__.cpython-38.pyc +0 -0
  44. package/knowledge_base/agents/__pycache__/fusion_agent.cpython-37.pyc +0 -0
  45. package/knowledge_base/agents/__pycache__/fusion_agent.cpython-38.pyc +0 -0
  46. package/knowledge_base/agents/__pycache__/graph.cpython-37.pyc +0 -0
  47. package/knowledge_base/agents/__pycache__/graph.cpython-38.pyc +0 -0
  48. package/knowledge_base/agents/__pycache__/healing_agent.cpython-37.pyc +0 -0
  49. package/knowledge_base/agents/__pycache__/intent_agent.cpython-37.pyc +0 -0
  50. package/knowledge_base/agents/__pycache__/intent_agent.cpython-38.pyc +0 -0
  51. package/knowledge_base/agents/__pycache__/kg_agent.cpython-37.pyc +0 -0
  52. package/knowledge_base/agents/__pycache__/kg_agent.cpython-38.pyc +0 -0
  53. package/knowledge_base/agents/__pycache__/memory_agent.cpython-37.pyc +0 -0
  54. package/knowledge_base/agents/__pycache__/memory_agent.cpython-38.pyc +0 -0
  55. package/knowledge_base/agents/__pycache__/rca_agent.cpython-37.pyc +0 -0
  56. package/knowledge_base/agents/__pycache__/reflection_agent.cpython-37.pyc +0 -0
  57. package/knowledge_base/agents/__pycache__/reflection_agent.cpython-38.pyc +0 -0
  58. package/knowledge_base/agents/__pycache__/regression_agent.cpython-37.pyc +0 -0
  59. package/knowledge_base/agents/__pycache__/retrieval_agent.cpython-37.pyc +0 -0
  60. package/knowledge_base/agents/__pycache__/retrieval_agent.cpython-38.pyc +0 -0
  61. package/knowledge_base/agents/__pycache__/script_agent.cpython-37.pyc +0 -0
  62. package/knowledge_base/agents/__pycache__/script_agent.cpython-38.pyc +0 -0
  63. package/knowledge_base/agents/__pycache__/state.cpython-37.pyc +0 -0
  64. package/knowledge_base/agents/__pycache__/state.cpython-38.pyc +0 -0
  65. package/knowledge_base/agents/__pycache__/temporal_agent.cpython-37.pyc +0 -0
  66. package/knowledge_base/agents/__pycache__/temporal_agent.cpython-38.pyc +0 -0
  67. package/knowledge_base/agents/__pycache__/validation_agent.cpython-37.pyc +0 -0
  68. package/knowledge_base/agents/__pycache__/validation_agent.cpython-38.pyc +0 -0
  69. package/knowledge_base/agents/automation_design_agent.py +123 -0
  70. package/knowledge_base/agents/code_intelligence_agent.py +147 -0
  71. package/knowledge_base/agents/confidence_agent.py +94 -0
  72. package/knowledge_base/agents/execution_validation_agent.py +129 -0
  73. package/knowledge_base/agents/feedback_agent.py +56 -0
  74. package/knowledge_base/agents/fusion_agent.py +113 -0
  75. package/knowledge_base/agents/graph.py +152 -0
  76. package/knowledge_base/agents/healing_agent.py +62 -0
  77. package/knowledge_base/agents/intent_agent.py +76 -0
  78. package/knowledge_base/agents/kg_agent.py +91 -0
  79. package/knowledge_base/agents/memory_agent.py +62 -0
  80. package/knowledge_base/agents/rca_agent.py +53 -0
  81. package/knowledge_base/agents/reflection_agent.py +62 -0
  82. package/knowledge_base/agents/regression_agent.py +81 -0
  83. package/knowledge_base/agents/requirement_agent.py +71 -0
  84. package/knowledge_base/agents/retrieval_agent.py +66 -0
  85. package/knowledge_base/agents/scenario_agent.py +81 -0
  86. package/knowledge_base/agents/script_agent.py +121 -0
  87. package/knowledge_base/agents/script_generator_agent.py +77 -0
  88. package/knowledge_base/agents/state.py +53 -0
  89. package/knowledge_base/agents/temporal_agent.py +48 -0
  90. package/knowledge_base/agents/testcase_agent.py +111 -0
  91. package/knowledge_base/agents/validation_agent.py +49 -0
  92. package/knowledge_base/agents/workflow_engine.py +87 -0
  93. package/knowledge_base/alembic/README.md +104 -0
  94. package/knowledge_base/alembic/__pycache__/env.cpython-37.pyc +0 -0
  95. package/knowledge_base/alembic/__pycache__/env.cpython-38.pyc +0 -0
  96. package/knowledge_base/alembic/env.py +76 -0
  97. package/knowledge_base/alembic/script.py.mako +5 -0
  98. package/knowledge_base/alembic/versions/.gitkeep +26 -0
  99. package/knowledge_base/alembic/versions/001_phase0_feedback.py +122 -0
  100. package/knowledge_base/alembic/versions/002_feedback_promotions.py +60 -0
  101. package/knowledge_base/alembic/versions/003_enterprise_hardening.py +74 -0
  102. package/knowledge_base/alembic/versions/__pycache__/001_phase0_feedback.cpython-37.pyc +0 -0
  103. package/knowledge_base/alembic/versions/__pycache__/001_phase0_feedback.cpython-38.pyc +0 -0
  104. package/knowledge_base/alembic/versions/__pycache__/002_feedback_promotions.cpython-37.pyc +0 -0
  105. package/knowledge_base/alembic/versions/__pycache__/002_feedback_promotions.cpython-38.pyc +0 -0
  106. package/knowledge_base/api/__init__.py +1 -0
  107. package/knowledge_base/api/__pycache__/__init__.cpython-37.pyc +0 -0
  108. package/knowledge_base/api/__pycache__/__init__.cpython-38.pyc +0 -0
  109. package/knowledge_base/api/__pycache__/middleware.cpython-38.pyc +0 -0
  110. package/knowledge_base/api/__pycache__/server.cpython-37.pyc +0 -0
  111. package/knowledge_base/api/__pycache__/server.cpython-38.pyc +0 -0
  112. package/knowledge_base/api/middleware.py +78 -0
  113. package/knowledge_base/api/server.py +778 -0
  114. package/knowledge_base/chunking/__init__.py +0 -0
  115. package/knowledge_base/chunking/__pycache__/__init__.cpython-37.pyc +0 -0
  116. package/knowledge_base/chunking/__pycache__/__init__.cpython-38.pyc +0 -0
  117. package/knowledge_base/chunking/__pycache__/chunker.cpython-37.pyc +0 -0
  118. package/knowledge_base/chunking/__pycache__/chunker.cpython-38.pyc +0 -0
  119. package/knowledge_base/chunking/__pycache__/validators.cpython-37.pyc +0 -0
  120. package/knowledge_base/chunking/__pycache__/validators.cpython-38.pyc +0 -0
  121. package/knowledge_base/chunking/chunker.py +402 -0
  122. package/knowledge_base/chunking/validators.py +144 -0
  123. package/knowledge_base/cli/__init__.py +1 -0
  124. package/knowledge_base/cli/__pycache__/__init__.cpython-37.pyc +0 -0
  125. package/knowledge_base/cli/__pycache__/__init__.cpython-38.pyc +0 -0
  126. package/knowledge_base/cli/__pycache__/feedback_cli.cpython-37.pyc +0 -0
  127. package/knowledge_base/cli/__pycache__/feedback_cli.cpython-38.pyc +0 -0
  128. package/knowledge_base/cli/__pycache__/generate.cpython-37.pyc +0 -0
  129. package/knowledge_base/cli/__pycache__/generate.cpython-38.pyc +0 -0
  130. package/knowledge_base/cli/__pycache__/generate_qcapps134.cpython-37.pyc +0 -0
  131. package/knowledge_base/cli/feedback_cli.py +121 -0
  132. package/knowledge_base/cli/generate.py +308 -0
  133. package/knowledge_base/cli/generate_qcapps134.py +391 -0
  134. package/knowledge_base/config/.env +100 -0
  135. package/knowledge_base/config/.env.example +75 -0
  136. package/knowledge_base/config/__init__.py +5 -0
  137. package/knowledge_base/config/__pycache__/__init__.cpython-37.pyc +0 -0
  138. package/knowledge_base/config/__pycache__/__init__.cpython-38.pyc +0 -0
  139. package/knowledge_base/config/__pycache__/settings.cpython-37.pyc +0 -0
  140. package/knowledge_base/config/__pycache__/settings.cpython-38.pyc +0 -0
  141. package/knowledge_base/config/settings.py +280 -0
  142. package/knowledge_base/connectors/__init__.py +5 -0
  143. package/knowledge_base/connectors/__pycache__/__init__.cpython-37.pyc +0 -0
  144. package/knowledge_base/connectors/__pycache__/__init__.cpython-38.pyc +0 -0
  145. package/knowledge_base/connectors/__pycache__/base.cpython-37.pyc +0 -0
  146. package/knowledge_base/connectors/__pycache__/base.cpython-38.pyc +0 -0
  147. package/knowledge_base/connectors/__pycache__/bitbucket_connector.cpython-37.pyc +0 -0
  148. package/knowledge_base/connectors/__pycache__/bitbucket_connector.cpython-38.pyc +0 -0
  149. package/knowledge_base/connectors/__pycache__/community_connector.cpython-38.pyc +0 -0
  150. package/knowledge_base/connectors/__pycache__/confluence_connector.cpython-37.pyc +0 -0
  151. package/knowledge_base/connectors/__pycache__/confluence_connector.cpython-38.pyc +0 -0
  152. package/knowledge_base/connectors/__pycache__/jira_connector.cpython-37.pyc +0 -0
  153. package/knowledge_base/connectors/__pycache__/jira_connector.cpython-38.pyc +0 -0
  154. package/knowledge_base/connectors/__pycache__/kb_igel_connector.cpython-38.pyc +0 -0
  155. package/knowledge_base/connectors/__pycache__/playwright_fetcher.cpython-38.pyc +0 -0
  156. package/knowledge_base/connectors/__pycache__/query_bridge.cpython-38.pyc +0 -0
  157. package/knowledge_base/connectors/__pycache__/search_backends.cpython-38.pyc +0 -0
  158. package/knowledge_base/connectors/__pycache__/serviceNow_connector.cpython-37.pyc +0 -0
  159. package/knowledge_base/connectors/__pycache__/serviceNow_connector.cpython-38.pyc +0 -0
  160. package/knowledge_base/connectors/__pycache__/sync_scheduler.cpython-37.pyc +0 -0
  161. package/knowledge_base/connectors/__pycache__/sync_scheduler.cpython-38.pyc +0 -0
  162. package/knowledge_base/connectors/__pycache__/teamcity_connector.cpython-37.pyc +0 -0
  163. package/knowledge_base/connectors/__pycache__/teamcity_connector.cpython-38.pyc +0 -0
  164. package/knowledge_base/connectors/__pycache__/testRail_connector.cpython-37.pyc +0 -0
  165. package/knowledge_base/connectors/__pycache__/testRail_connector.cpython-38.pyc +0 -0
  166. package/knowledge_base/connectors/__pycache__/web_fetcher.cpython-38.pyc +0 -0
  167. package/knowledge_base/connectors/base.py +44 -0
  168. package/knowledge_base/connectors/bitbucket_connector.py +25 -0
  169. package/knowledge_base/connectors/community_connector.py +112 -0
  170. package/knowledge_base/connectors/confluence_connector.py +165 -0
  171. package/knowledge_base/connectors/jira_connector.py +174 -0
  172. package/knowledge_base/connectors/kb_igel_connector.py +145 -0
  173. package/knowledge_base/connectors/playwright_fetcher.py +50 -0
  174. package/knowledge_base/connectors/query_bridge.py +170 -0
  175. package/knowledge_base/connectors/search_backends.py +368 -0
  176. package/knowledge_base/connectors/serviceNow_connector.py +25 -0
  177. package/knowledge_base/connectors/sync_scheduler.py +111 -0
  178. package/knowledge_base/connectors/teamcity_connector.py +25 -0
  179. package/knowledge_base/connectors/testRail_connector.py +25 -0
  180. package/knowledge_base/connectors/web_fetcher.py +280 -0
  181. package/knowledge_base/core/__init__.py +8 -0
  182. package/knowledge_base/core/__pycache__/__init__.cpython-37.pyc +0 -0
  183. package/knowledge_base/core/__pycache__/__init__.cpython-38.pyc +0 -0
  184. package/knowledge_base/core/__pycache__/cache.cpython-38.pyc +0 -0
  185. package/knowledge_base/core/__pycache__/circuit_breaker.cpython-38.pyc +0 -0
  186. package/knowledge_base/core/__pycache__/confidence.cpython-37.pyc +0 -0
  187. package/knowledge_base/core/__pycache__/confidence.cpython-38.pyc +0 -0
  188. package/knowledge_base/core/__pycache__/logging_setup.cpython-38.pyc +0 -0
  189. package/knowledge_base/core/__pycache__/metrics.cpython-38.pyc +0 -0
  190. package/knowledge_base/core/__pycache__/precedence.cpython-37.pyc +0 -0
  191. package/knowledge_base/core/__pycache__/precedence.cpython-38.pyc +0 -0
  192. package/knowledge_base/core/__pycache__/request_context.cpython-38.pyc +0 -0
  193. package/knowledge_base/core/cache.py +98 -0
  194. package/knowledge_base/core/circuit_breaker.py +33 -0
  195. package/knowledge_base/core/confidence.py +254 -0
  196. package/knowledge_base/core/logging_setup.py +39 -0
  197. package/knowledge_base/core/metrics.py +63 -0
  198. package/knowledge_base/core/precedence.py +354 -0
  199. package/knowledge_base/core/request_context.py +54 -0
  200. package/knowledge_base/db/__init__.py +0 -0
  201. package/knowledge_base/db/__pycache__/__init__.cpython-314.pyc +0 -0
  202. package/knowledge_base/db/__pycache__/__init__.cpython-37.pyc +0 -0
  203. package/knowledge_base/db/__pycache__/__init__.cpython-38.pyc +0 -0
  204. package/knowledge_base/db/__pycache__/client.cpython-314.pyc +0 -0
  205. package/knowledge_base/db/__pycache__/client.cpython-37.pyc +0 -0
  206. package/knowledge_base/db/__pycache__/client.cpython-38.pyc +0 -0
  207. package/knowledge_base/db/__pycache__/connector_sync.cpython-38.pyc +0 -0
  208. package/knowledge_base/db/__pycache__/feedback_schema.cpython-37.pyc +0 -0
  209. package/knowledge_base/db/__pycache__/feedback_schema.cpython-38.pyc +0 -0
  210. package/knowledge_base/db/__pycache__/kg_introspect.cpython-314.pyc +0 -0
  211. package/knowledge_base/db/__pycache__/kg_introspect.cpython-37.pyc +0 -0
  212. package/knowledge_base/db/__pycache__/kg_introspect.cpython-38.pyc +0 -0
  213. package/knowledge_base/db/__pycache__/schema.cpython-37.pyc +0 -0
  214. package/knowledge_base/db/__pycache__/schema.cpython-38.pyc +0 -0
  215. package/knowledge_base/db/client.py +100 -0
  216. package/knowledge_base/db/connector_sync.py +43 -0
  217. package/knowledge_base/db/feedback_schema.py +548 -0
  218. package/knowledge_base/db/igel_schema.py +150 -0
  219. package/knowledge_base/db/kg_introspect.py +116 -0
  220. package/knowledge_base/db/migrations.py +22 -0
  221. package/knowledge_base/db/schema.py +649 -0
  222. package/knowledge_base/deploy/Dockerfile +23 -0
  223. package/knowledge_base/deploy/feedback_automation.sh +18 -0
  224. package/knowledge_base/deploy/firecrawl-docker.sh +50 -0
  225. package/knowledge_base/deploy/igel-testgen.service +26 -0
  226. package/knowledge_base/deploy/install_reranker.sh +92 -0
  227. package/knowledge_base/deploy/requirements.txt +68 -0
  228. package/knowledge_base/deploy/run_server.sh +35 -0
  229. package/knowledge_base/docs/TEST_GENERATION_GUIDE.md +174 -0
  230. package/knowledge_base/embeddings/__init__.py +0 -0
  231. package/knowledge_base/embeddings/__pycache__/__init__.cpython-314.pyc +0 -0
  232. package/knowledge_base/embeddings/__pycache__/__init__.cpython-37.pyc +0 -0
  233. package/knowledge_base/embeddings/__pycache__/__init__.cpython-38.pyc +0 -0
  234. package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-314.pyc +0 -0
  235. package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-37.pyc +0 -0
  236. package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-38.pyc +0 -0
  237. package/knowledge_base/embeddings/azure_embedder.py +99 -0
  238. package/knowledge_base/eval/__init__.py +33 -0
  239. package/knowledge_base/eval/__main__.py +172 -0
  240. package/knowledge_base/eval/__pycache__/__init__.cpython-37.pyc +0 -0
  241. package/knowledge_base/eval/__pycache__/__init__.cpython-38.pyc +0 -0
  242. package/knowledge_base/eval/__pycache__/__main__.cpython-37.pyc +0 -0
  243. package/knowledge_base/eval/__pycache__/baseline.cpython-37.pyc +0 -0
  244. package/knowledge_base/eval/__pycache__/baseline.cpython-38.pyc +0 -0
  245. package/knowledge_base/eval/__pycache__/harness.cpython-37.pyc +0 -0
  246. package/knowledge_base/eval/__pycache__/harness.cpython-38.pyc +0 -0
  247. package/knowledge_base/eval/__pycache__/scorers.cpython-37.pyc +0 -0
  248. package/knowledge_base/eval/__pycache__/scorers.cpython-38.pyc +0 -0
  249. package/knowledge_base/eval/baseline.py +213 -0
  250. package/knowledge_base/eval/harness.py +400 -0
  251. package/knowledge_base/eval/queries_50.json +302 -0
  252. package/knowledge_base/eval/scorers.py +494 -0
  253. package/knowledge_base/exemplars/README.md +7 -0
  254. package/knowledge_base/exemplars/__pycache__/pytest_reference_snippet.cpython-37.pyc +0 -0
  255. package/knowledge_base/exemplars/gold_manual_concise_snippet.md +8 -0
  256. package/knowledge_base/exemplars/gold_manual_detailed_snippet.md +7 -0
  257. package/knowledge_base/exemplars/pytest_reference_snippet.py +37 -0
  258. package/knowledge_base/feedback/__init__.py +10 -0
  259. package/knowledge_base/feedback/__pycache__/__init__.cpython-37.pyc +0 -0
  260. package/knowledge_base/feedback/__pycache__/__init__.cpython-38.pyc +0 -0
  261. package/knowledge_base/feedback/__pycache__/analytics.cpython-37.pyc +0 -0
  262. package/knowledge_base/feedback/__pycache__/analytics.cpython-38.pyc +0 -0
  263. package/knowledge_base/feedback/__pycache__/applier.cpython-37.pyc +0 -0
  264. package/knowledge_base/feedback/__pycache__/applier.cpython-38.pyc +0 -0
  265. package/knowledge_base/feedback/__pycache__/curator.cpython-37.pyc +0 -0
  266. package/knowledge_base/feedback/__pycache__/curator.cpython-38.pyc +0 -0
  267. package/knowledge_base/feedback/__pycache__/handler.cpython-37.pyc +0 -0
  268. package/knowledge_base/feedback/__pycache__/handler.cpython-38.pyc +0 -0
  269. package/knowledge_base/feedback/analytics.py +72 -0
  270. package/knowledge_base/feedback/applier.py +93 -0
  271. package/knowledge_base/feedback/curator.py +49 -0
  272. package/knowledge_base/feedback/handler.py +151 -0
  273. package/knowledge_base/generation/__init__.py +5 -0
  274. package/knowledge_base/generation/__pycache__/__init__.cpython-37.pyc +0 -0
  275. package/knowledge_base/generation/__pycache__/__init__.cpython-38.pyc +0 -0
  276. package/knowledge_base/generation/__pycache__/input_classifier.cpython-37.pyc +0 -0
  277. package/knowledge_base/generation/__pycache__/input_classifier.cpython-38.pyc +0 -0
  278. package/knowledge_base/generation/__pycache__/manual_tc_parser.cpython-37.pyc +0 -0
  279. package/knowledge_base/generation/__pycache__/manual_tc_parser.cpython-38.pyc +0 -0
  280. package/knowledge_base/generation/__pycache__/prompts.cpython-37.pyc +0 -0
  281. package/knowledge_base/generation/__pycache__/prompts.cpython-38.pyc +0 -0
  282. package/knowledge_base/generation/__pycache__/tc_transformer.cpython-37.pyc +0 -0
  283. package/knowledge_base/generation/__pycache__/test_generator.cpython-37.pyc +0 -0
  284. package/knowledge_base/generation/__pycache__/test_generator.cpython-38.pyc +0 -0
  285. package/knowledge_base/generation/input_classifier.py +148 -0
  286. package/knowledge_base/generation/manual_tc_parser.py +180 -0
  287. package/knowledge_base/generation/prompts.py +755 -0
  288. package/knowledge_base/generation/tc_transformer.py +368 -0
  289. package/knowledge_base/generation/test_generator.py +553 -0
  290. package/knowledge_base/graph_db/__init__.py +5 -0
  291. package/knowledge_base/graph_db/__pycache__/__init__.cpython-37.pyc +0 -0
  292. package/knowledge_base/graph_db/__pycache__/__init__.cpython-38.pyc +0 -0
  293. package/knowledge_base/graph_db/__pycache__/graphiti_client.cpython-37.pyc +0 -0
  294. package/knowledge_base/graph_db/__pycache__/kg_traversal.cpython-37.pyc +0 -0
  295. package/knowledge_base/graph_db/__pycache__/migrate_kg.cpython-37.pyc +0 -0
  296. package/knowledge_base/graph_db/__pycache__/neo4j_client.cpython-37.pyc +0 -0
  297. package/knowledge_base/graph_db/__pycache__/neo4j_client.cpython-38.pyc +0 -0
  298. package/knowledge_base/graph_db/__pycache__/schema.cpython-37.pyc +0 -0
  299. package/knowledge_base/graph_db/__pycache__/temporal_ingest.cpython-37.pyc +0 -0
  300. package/knowledge_base/graph_db/graphiti_client.py +127 -0
  301. package/knowledge_base/graph_db/kg_traversal.py +192 -0
  302. package/knowledge_base/graph_db/migrate_kg.py +93 -0
  303. package/knowledge_base/graph_db/neo4j_client.py +116 -0
  304. package/knowledge_base/graph_db/schema.py +22 -0
  305. package/knowledge_base/graph_db/temporal_ingest.py +59 -0
  306. package/knowledge_base/ingestion/__init__.py +1 -0
  307. package/knowledge_base/ingestion/__pycache__/__init__.cpython-37.pyc +0 -0
  308. package/knowledge_base/ingestion/__pycache__/__init__.cpython-38.pyc +0 -0
  309. package/knowledge_base/ingestion/__pycache__/ingest.cpython-37.pyc +0 -0
  310. package/knowledge_base/ingestion/__pycache__/ingest.cpython-38.pyc +0 -0
  311. package/knowledge_base/ingestion/__pycache__/ingest_images.cpython-37.pyc +0 -0
  312. package/knowledge_base/ingestion/__pycache__/ingest_images.cpython-38.pyc +0 -0
  313. package/knowledge_base/ingestion/__pycache__/kg_extractor.cpython-37.pyc +0 -0
  314. package/knowledge_base/ingestion/__pycache__/kg_extractor.cpython-38.pyc +0 -0
  315. package/knowledge_base/ingestion/__pycache__/web_ingest.cpython-38.pyc +0 -0
  316. package/knowledge_base/ingestion/ingest.py +346 -0
  317. package/knowledge_base/ingestion/ingest_images.py +330 -0
  318. package/knowledge_base/ingestion/kg_extractor.py +358 -0
  319. package/knowledge_base/ingestion/web_ingest.py +83 -0
  320. package/knowledge_base/intelligence/__init__.py +1 -0
  321. package/knowledge_base/intelligence/__pycache__/__init__.cpython-37.pyc +0 -0
  322. package/knowledge_base/intelligence/__pycache__/execution_ingest.cpython-37.pyc +0 -0
  323. package/knowledge_base/intelligence/__pycache__/predictive_engine.cpython-37.pyc +0 -0
  324. package/knowledge_base/intelligence/__pycache__/risk_scorer.cpython-37.pyc +0 -0
  325. package/knowledge_base/intelligence/execution_ingest.py +34 -0
  326. package/knowledge_base/intelligence/predictive_engine.py +7 -0
  327. package/knowledge_base/intelligence/risk_scorer.py +8 -0
  328. package/knowledge_base/memory/__init__.py +6 -0
  329. package/knowledge_base/memory/__pycache__/__init__.cpython-37.pyc +0 -0
  330. package/knowledge_base/memory/__pycache__/__init__.cpython-38.pyc +0 -0
  331. package/knowledge_base/memory/__pycache__/cognee_client.cpython-37.pyc +0 -0
  332. package/knowledge_base/memory/__pycache__/cognee_client.cpython-38.pyc +0 -0
  333. package/knowledge_base/memory/__pycache__/ingest_memory.cpython-37.pyc +0 -0
  334. package/knowledge_base/memory/__pycache__/memory_types.cpython-37.pyc +0 -0
  335. package/knowledge_base/memory/__pycache__/memory_types.cpython-38.pyc +0 -0
  336. package/knowledge_base/memory/cognee_client.py +164 -0
  337. package/knowledge_base/memory/ingest_memory.py +60 -0
  338. package/knowledge_base/memory/memory_types.py +23 -0
  339. package/knowledge_base/models/test_requirement.py +84 -0
  340. package/knowledge_base/parsers/__init__.py +0 -0
  341. package/knowledge_base/parsers/__pycache__/__init__.cpython-37.pyc +0 -0
  342. package/knowledge_base/parsers/__pycache__/__init__.cpython-38.pyc +0 -0
  343. package/knowledge_base/parsers/__pycache__/csv_parser.cpython-37.pyc +0 -0
  344. package/knowledge_base/parsers/__pycache__/csv_parser.cpython-38.pyc +0 -0
  345. package/knowledge_base/parsers/__pycache__/doc_parser.cpython-37.pyc +0 -0
  346. package/knowledge_base/parsers/__pycache__/doc_parser.cpython-38.pyc +0 -0
  347. package/knowledge_base/parsers/__pycache__/html_parser.cpython-38.pyc +0 -0
  348. package/knowledge_base/parsers/__pycache__/image_extractor.cpython-37.pyc +0 -0
  349. package/knowledge_base/parsers/__pycache__/image_extractor.cpython-38.pyc +0 -0
  350. package/knowledge_base/parsers/__pycache__/markdown_parser.cpython-37.pyc +0 -0
  351. package/knowledge_base/parsers/__pycache__/markdown_parser.cpython-38.pyc +0 -0
  352. package/knowledge_base/parsers/__pycache__/pdf_parser.cpython-37.pyc +0 -0
  353. package/knowledge_base/parsers/__pycache__/pdf_parser.cpython-38.pyc +0 -0
  354. package/knowledge_base/parsers/csv_parser.py +126 -0
  355. package/knowledge_base/parsers/doc_parser.py +176 -0
  356. package/knowledge_base/parsers/html_parser.py +151 -0
  357. package/knowledge_base/parsers/image_extractor.py +370 -0
  358. package/knowledge_base/parsers/markdown_parser.py +116 -0
  359. package/knowledge_base/parsers/pdf_parser.py +178 -0
  360. package/knowledge_base/retrieval/__init__.py +1 -0
  361. package/knowledge_base/retrieval/__pycache__/__init__.cpython-37.pyc +0 -0
  362. package/knowledge_base/retrieval/__pycache__/__init__.cpython-38.pyc +0 -0
  363. package/knowledge_base/retrieval/__pycache__/context_fusion.cpython-37.pyc +0 -0
  364. package/knowledge_base/retrieval/__pycache__/context_fusion.cpython-38.pyc +0 -0
  365. package/knowledge_base/retrieval/__pycache__/knowledge_graph.cpython-37.pyc +0 -0
  366. package/knowledge_base/retrieval/__pycache__/knowledge_graph.cpython-38.pyc +0 -0
  367. package/knowledge_base/retrieval/__pycache__/reranker.cpython-37.pyc +0 -0
  368. package/knowledge_base/retrieval/__pycache__/reranker.cpython-38.pyc +0 -0
  369. package/knowledge_base/retrieval/__pycache__/retrieval_debug.cpython-37.pyc +0 -0
  370. package/knowledge_base/retrieval/__pycache__/retrieval_debug.cpython-38.pyc +0 -0
  371. package/knowledge_base/retrieval/__pycache__/retriever.cpython-37.pyc +0 -0
  372. package/knowledge_base/retrieval/__pycache__/retriever.cpython-38.pyc +0 -0
  373. package/knowledge_base/retrieval/__pycache__/unified_query.cpython-38.pyc +0 -0
  374. package/knowledge_base/retrieval/__pycache__/web_retriever.cpython-38.pyc +0 -0
  375. package/knowledge_base/retrieval/context_fusion.py +264 -0
  376. package/knowledge_base/retrieval/knowledge_graph.py +267 -0
  377. package/knowledge_base/retrieval/reranker.py +308 -0
  378. package/knowledge_base/retrieval/retrieval_debug.py +69 -0
  379. package/knowledge_base/retrieval/retriever.py +885 -0
  380. package/knowledge_base/retrieval/unified_query.py +189 -0
  381. package/knowledge_base/retrieval/web_retriever.py +337 -0
  382. package/knowledge_base/validators/__init__.py +1 -0
  383. package/knowledge_base/validators/__pycache__/__init__.cpython-314.pyc +0 -0
  384. package/knowledge_base/validators/__pycache__/__init__.cpython-37.pyc +0 -0
  385. package/knowledge_base/validators/__pycache__/step_fidelity.cpython-314.pyc +0 -0
  386. package/knowledge_base/validators/__pycache__/step_fidelity.cpython-37.pyc +0 -0
  387. package/knowledge_base/validators/step_fidelity.py +88 -0
  388. package/knowledge_base/vision/__init__.py +0 -0
  389. package/knowledge_base/vision/__pycache__/__init__.cpython-37.pyc +0 -0
  390. package/knowledge_base/vision/__pycache__/__init__.cpython-38.pyc +0 -0
  391. package/knowledge_base/vision/__pycache__/image_describer.cpython-37.pyc +0 -0
  392. package/knowledge_base/vision/__pycache__/image_describer.cpython-38.pyc +0 -0
  393. package/knowledge_base/vision/image_describer.py +202 -0
  394. package/package.json +31 -0
@@ -0,0 +1,170 @@
1
+ """Live query bridge — MCP / external connectors at query time."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from dataclasses import dataclass, field
7
+ from time import perf_counter
8
+
9
+ from knowledge_base.config import cfg
10
+ from knowledge_base.connectors.base import BaseConnector
11
+ from knowledge_base.core.circuit_breaker import is_open, record_failure, record_success
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @dataclass
17
+ class ExternalHit:
18
+ source: str
19
+ title: str
20
+ url: str
21
+ content: str
22
+ score: float = 0.5
23
+ source_type: str = "external"
24
+
25
+
26
+ @dataclass
27
+ class ExternalContext:
28
+ hits: list[ExternalHit] = field(default_factory=list)
29
+ block: str = ""
30
+ sources_queried: list[str] = field(default_factory=list)
31
+ timing_ms: float = 0.0
32
+
33
+
34
+ def _mcp_sources() -> list[str]:
35
+ raw = getattr(cfg, "QUERY_MCP_SOURCES", ("jira", "confluence"))
36
+ if isinstance(raw, str):
37
+ return [s.strip().lower() for s in raw.split(",") if s.strip()]
38
+ return [s.strip().lower() for s in raw if s.strip()]
39
+
40
+
41
+ def _connector_for(name: str):
42
+ # Keep this registry aligned with sync_scheduler CONNECTORS.
43
+ if name == "jira":
44
+ from knowledge_base.connectors.jira_connector import JiraConnector
45
+ return JiraConnector()
46
+ if name == "confluence":
47
+ from knowledge_base.connectors.confluence_connector import ConfluenceConnector
48
+ return ConfluenceConnector()
49
+ if name == "bitbucket":
50
+ from knowledge_base.connectors.bitbucket_connector import BitbucketConnector
51
+ return BitbucketConnector()
52
+ if name == "teamcity":
53
+ from knowledge_base.connectors.teamcity_connector import TeamCityConnector
54
+ return TeamCityConnector()
55
+ if name == "testrail":
56
+ from knowledge_base.connectors.testRail_connector import TestRailConnector
57
+ return TestRailConnector()
58
+ if name == "servicenow":
59
+ from knowledge_base.connectors.serviceNow_connector import ServiceNowConnector
60
+ return ServiceNowConnector()
61
+ if name == "kb_igel":
62
+ from knowledge_base.connectors.kb_igel_connector import KbIgelConnector
63
+ return KbIgelConnector()
64
+ if name == "community":
65
+ from knowledge_base.connectors.community_connector import CommunityConnector
66
+ return CommunityConnector()
67
+ logger.warning("Unsupported MCP source configured: %s", name)
68
+ return None
69
+
70
+
71
+ def _has_query_impl(connector: BaseConnector) -> bool:
72
+ """True when connector overrides query-time methods instead of base no-op stubs."""
73
+ return (
74
+ connector.__class__.search is not BaseConnector.search
75
+ or connector.__class__.fetch_page is not BaseConnector.fetch_page
76
+ )
77
+
78
+
79
+ def query_mcp_sources(
80
+ query: str,
81
+ *,
82
+ max_hits: int = 3,
83
+ timeout_seconds: float | None = None,
84
+ ) -> ExternalContext:
85
+ if not getattr(cfg, "QUERY_EXTERNAL_ENABLED", True) or not query.strip():
86
+ return ExternalContext()
87
+
88
+ hits: list[ExternalHit] = []
89
+ queried: list[str] = []
90
+ t0 = perf_counter()
91
+ deadline = perf_counter() + timeout_seconds if timeout_seconds and timeout_seconds > 0 else None
92
+
93
+ for name in _mcp_sources():
94
+ if deadline is not None and perf_counter() >= deadline:
95
+ logger.warning("MCP query budget exhausted before source=%s", name)
96
+ break
97
+ if len(hits) >= max_hits:
98
+ break
99
+ if is_open(name):
100
+ continue
101
+ connector = _connector_for(name)
102
+ if connector is None or not getattr(connector, "enabled", False):
103
+ continue
104
+ if not _has_query_impl(connector):
105
+ continue
106
+ queried.append(name)
107
+ try:
108
+ source_success = False
109
+ remaining = None
110
+ if deadline is not None:
111
+ remaining = max(0.05, deadline - perf_counter())
112
+ if remaining <= 0:
113
+ logger.warning("MCP query budget exhausted before search for source=%s", name)
114
+ break
115
+
116
+ urls = connector.search(query, limit=max_hits, timeout=remaining)
117
+ if urls:
118
+ for url in urls[:max_hits]:
119
+ if deadline is not None and perf_counter() >= deadline:
120
+ logger.warning("MCP query budget exhausted while processing source=%s", name)
121
+ break
122
+ if len(hits) >= max_hits:
123
+ break
124
+ remaining = None
125
+ if deadline is not None:
126
+ remaining = max(0.05, deadline - perf_counter())
127
+ if remaining <= 0:
128
+ logger.warning("MCP query budget exhausted before fetch_page for source=%s", name)
129
+ break
130
+
131
+ page = connector.fetch_page(url, timeout=remaining)
132
+ title = url
133
+ if page:
134
+ content = (page.get("raw_text") or page.get("content") or "").strip()[:6000]
135
+ title = page.get("title") or url
136
+ if content:
137
+ source_success = True
138
+ hits.append(ExternalHit(
139
+ source=name,
140
+ title=title,
141
+ url=url,
142
+ content=content,
143
+ source_type=page.get("source_type", name),
144
+ ))
145
+ elif not urls:
146
+ logger.debug("MCP source %s returned no URLs", name)
147
+
148
+ if source_success:
149
+ record_success(name)
150
+ except Exception as e:
151
+ record_failure(name)
152
+ logger.warning("MCP source %s query failed: %s", name, e)
153
+
154
+ block = _format_block(hits)
155
+ elapsed = (perf_counter() - t0) * 1000
156
+ return ExternalContext(hits=hits, block=block, sources_queried=queried, timing_ms=elapsed)
157
+
158
+
159
+ def _format_block(hits: list[ExternalHit], *, max_chars: int = 4000) -> str:
160
+ if not hits:
161
+ return ""
162
+ lines = ["—— MCP / external connector sources ——"]
163
+ used = 0
164
+ for hit in hits:
165
+ block = f"\n### [{hit.source}] {hit.title}\nURL: {hit.url}\n{hit.content[:2500]}"
166
+ if used + len(block) > max_chars:
167
+ break
168
+ lines.append(block)
169
+ used += len(block)
170
+ return "\n".join(lines)
@@ -0,0 +1,368 @@
1
+ """Free/open-source web search backends for Stage 2 live web retrieval."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from abc import ABC, abstractmethod
7
+ from dataclasses import dataclass, field
8
+ from typing import Any
9
+ from urllib.parse import quote_plus, urlparse
10
+
11
+ from knowledge_base.config import cfg
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @dataclass
17
+ class WebSearchResult:
18
+ url: str
19
+ title: str
20
+ snippet: str = ""
21
+ backend: str = ""
22
+ metadata: dict[str, Any] = field(default_factory=dict)
23
+
24
+
25
+ class SearchBackend(ABC):
26
+ name: str = "base"
27
+
28
+ @abstractmethod
29
+ def search(
30
+ self,
31
+ query: str,
32
+ *,
33
+ max_results: int = 5,
34
+ include_domains: tuple[str, ...] | None = None,
35
+ exclude_domains: tuple[str, ...] | None = None,
36
+ ) -> list[WebSearchResult]:
37
+ raise NotImplementedError
38
+
39
+
40
+ def _normalize_url(url: str) -> str:
41
+ parsed = urlparse(url.strip())
42
+ path = parsed.path.rstrip("/") or "/"
43
+ return f"{parsed.scheme}://{parsed.netloc.lower()}{path}"
44
+
45
+
46
+ def _host_matches(host: str, pattern: str) -> bool:
47
+ p = pattern.strip().lower().lstrip(".")
48
+ if not p:
49
+ return False
50
+ return host == p or host.endswith("." + p)
51
+
52
+
53
+ def _domain_allowed(
54
+ url: str,
55
+ include_domains: tuple[str, ...] | None,
56
+ exclude_domains: tuple[str, ...] | None,
57
+ ) -> bool:
58
+ host = urlparse(url).netloc.lower()
59
+ if not host:
60
+ return False
61
+ if exclude_domains:
62
+ for pattern in exclude_domains:
63
+ if _host_matches(host, pattern):
64
+ return False
65
+ if include_domains:
66
+ return any(_host_matches(host, p) for p in include_domains if p.strip())
67
+ return True
68
+
69
+
70
+ class DuckDuckGoBackend(SearchBackend):
71
+ """Organic web search via duckduckgo-search / ddgs (free, no API key)."""
72
+
73
+ name = "ddgs"
74
+
75
+ def search(
76
+ self,
77
+ query: str,
78
+ *,
79
+ max_results: int = 5,
80
+ include_domains: tuple[str, ...] | None = None,
81
+ exclude_domains: tuple[str, ...] | None = None,
82
+ ) -> list[WebSearchResult]:
83
+ if not query.strip():
84
+ return []
85
+
86
+ backend = getattr(cfg, "DDGS_BACKEND", "auto")
87
+ region = getattr(cfg, "DDGS_REGION", "wt-wt")
88
+ fetch_n = max(max_results * 3, max_results + 2)
89
+
90
+ raw_rows: list[dict[str, str]] = []
91
+ backends_to_try = [backend] if backend not in ("auto", "") else ["api", "html", "lite"]
92
+ last_err: Exception | None = None
93
+
94
+ try:
95
+ try:
96
+ from ddgs import DDGS
97
+ except ImportError:
98
+ from duckduckgo_search import DDGS
99
+
100
+ ddgs = DDGS()
101
+ search_fn = getattr(ddgs, "text", None)
102
+ if search_fn is None:
103
+ return []
104
+
105
+ for be in backends_to_try:
106
+ try:
107
+ try:
108
+ raw_rows = list(
109
+ search_fn(
110
+ query,
111
+ region=region,
112
+ safesearch="moderate",
113
+ max_results=fetch_n,
114
+ backend=be,
115
+ )
116
+ or []
117
+ )
118
+ except TypeError:
119
+ raw_rows = list(
120
+ search_fn(
121
+ query,
122
+ region=region,
123
+ safesearch="moderate",
124
+ max_results=fetch_n,
125
+ )
126
+ or []
127
+ )
128
+ if raw_rows:
129
+ break
130
+ except Exception as e:
131
+ last_err = e
132
+ logger.debug("DuckDuckGo backend=%s failed: %s", be, e)
133
+ continue
134
+
135
+ if not raw_rows and last_err is not None:
136
+ logger.warning("DuckDuckGo search failed after retries: %s", last_err)
137
+ return []
138
+ except Exception as e:
139
+ logger.warning("DuckDuckGo search failed: %s", e)
140
+ return []
141
+
142
+ results: list[WebSearchResult] = []
143
+ seen: set[str] = set()
144
+ for row in raw_rows:
145
+ url = (row.get("href") or row.get("url") or "").strip()
146
+ if not url or not url.startswith("http"):
147
+ continue
148
+ norm = _normalize_url(url)
149
+ if norm in seen:
150
+ continue
151
+ if not _domain_allowed(url, include_domains, exclude_domains):
152
+ continue
153
+ seen.add(norm)
154
+ results.append(
155
+ WebSearchResult(
156
+ url=url,
157
+ title=(row.get("title") or url)[:300],
158
+ snippet=(row.get("body") or row.get("snippet") or "")[:2000],
159
+ backend=self.name,
160
+ )
161
+ )
162
+ if len(results) >= max_results:
163
+ break
164
+ logger.info("DuckDuckGoBackend: %d results for %r", len(results), query[:60])
165
+ return results
166
+
167
+
168
+ class SearXNGBackend(SearchBackend):
169
+ """Self-hosted SearXNG metasearch (free, configurable, no promoted results)."""
170
+
171
+ name = "searxng"
172
+
173
+ def __init__(self) -> None:
174
+ self.base_url = (getattr(cfg, "SEARXNG_BASE_URL", "") or "").rstrip("/")
175
+
176
+ @property
177
+ def enabled(self) -> bool:
178
+ return bool(self.base_url)
179
+
180
+ def search(
181
+ self,
182
+ query: str,
183
+ *,
184
+ max_results: int = 5,
185
+ include_domains: tuple[str, ...] | None = None,
186
+ exclude_domains: tuple[str, ...] | None = None,
187
+ ) -> list[WebSearchResult]:
188
+ if not self.enabled or not query.strip():
189
+ return []
190
+
191
+ api = f"{self.base_url}/search?q={quote_plus(query)}&format=json"
192
+ try:
193
+ import httpx
194
+
195
+ with httpx.Client(follow_redirects=True, timeout=20.0) as client:
196
+ resp = client.get(api)
197
+ if resp.status_code != 200:
198
+ logger.warning("SearXNG HTTP %s for %s", resp.status_code, api)
199
+ return []
200
+ data = resp.json()
201
+ except Exception as e:
202
+ logger.warning("SearXNG search failed: %s", e)
203
+ return []
204
+
205
+ results: list[WebSearchResult] = []
206
+ seen: set[str] = set()
207
+ for item in data.get("results", []):
208
+ url = (item.get("url") or "").strip()
209
+ if not url.startswith("http"):
210
+ continue
211
+ norm = _normalize_url(url)
212
+ if norm in seen:
213
+ continue
214
+ if not _domain_allowed(url, include_domains, exclude_domains):
215
+ continue
216
+ seen.add(norm)
217
+ results.append(
218
+ WebSearchResult(
219
+ url=url,
220
+ title=(item.get("title") or url)[:300],
221
+ snippet=(item.get("content") or item.get("snippet") or "")[:2000],
222
+ backend=self.name,
223
+ )
224
+ )
225
+ if len(results) >= max_results:
226
+ break
227
+ logger.info("SearXNGBackend: %d results for %r", len(results), query[:60])
228
+ return results
229
+
230
+
231
+ class FirecrawlBackend(SearchBackend):
232
+ """Web search via self-hosted Firecrawl (search + optional markdown in results)."""
233
+
234
+ name = "firecrawl"
235
+
236
+ def __init__(self) -> None:
237
+ self.base_url = (getattr(cfg, "FIRECRAWL_BASE_URL", "") or "").rstrip("/")
238
+ self.api_key = getattr(cfg, "FIRECRAWL_API_KEY", "") or ""
239
+
240
+ @property
241
+ def enabled(self) -> bool:
242
+ return bool(self.base_url)
243
+
244
+ def _search_sdk(self, query: str, max_results: int) -> list[dict[str, Any]]:
245
+ from firecrawl import Firecrawl
246
+
247
+ init_kwargs: dict[str, Any] = {}
248
+ if self.api_key:
249
+ init_kwargs["api_key"] = self.api_key
250
+
251
+ app = None
252
+ for param in ("api_url", "base_url"):
253
+ try:
254
+ app = Firecrawl(**{param: self.base_url, **init_kwargs})
255
+ break
256
+ except TypeError:
257
+ continue
258
+ if app is None:
259
+ app = Firecrawl(**init_kwargs)
260
+
261
+ resp = app.search(query, limit=max_results)
262
+ if isinstance(resp, dict):
263
+ data = resp.get("data") or resp.get("results") or []
264
+ elif isinstance(resp, list):
265
+ data = resp
266
+ else:
267
+ data = getattr(resp, "data", None) or []
268
+ return list(data or [])
269
+
270
+ def _search_rest(self, query: str, max_results: int) -> list[dict[str, Any]]:
271
+ import httpx
272
+
273
+ url = f"{self.base_url}/v1/search"
274
+ headers = {"Content-Type": "application/json"}
275
+ if self.api_key:
276
+ headers["Authorization"] = f"Bearer {self.api_key}"
277
+ payload = {"query": query, "limit": max_results}
278
+ with httpx.Client(follow_redirects=True, timeout=60.0) as client:
279
+ resp = client.post(url, json=payload, headers=headers)
280
+ if resp.status_code != 200:
281
+ logger.warning("Firecrawl search HTTP %s: %s", resp.status_code, resp.text[:200])
282
+ return []
283
+ body = resp.json()
284
+ if isinstance(body, dict):
285
+ return list(body.get("data") or body.get("results") or [])
286
+ return []
287
+
288
+ def search(
289
+ self,
290
+ query: str,
291
+ *,
292
+ max_results: int = 5,
293
+ include_domains: tuple[str, ...] | None = None,
294
+ exclude_domains: tuple[str, ...] | None = None,
295
+ ) -> list[WebSearchResult]:
296
+ if not self.enabled or not query.strip():
297
+ return []
298
+
299
+ raw_items: list[dict[str, Any]] = []
300
+ try:
301
+ raw_items = self._search_sdk(query, max_results=max_results)
302
+ except ImportError:
303
+ logger.debug("firecrawl-py not installed — using REST search")
304
+ except Exception as e:
305
+ logger.debug("Firecrawl SDK search failed: %s — trying REST", e)
306
+
307
+ if not raw_items:
308
+ try:
309
+ raw_items = self._search_rest(query, max_results=max_results)
310
+ except Exception as e:
311
+ logger.warning("Firecrawl REST search failed: %s", e)
312
+ return []
313
+
314
+ results: list[WebSearchResult] = []
315
+ seen: set[str] = set()
316
+ for item in raw_items:
317
+ url = (item.get("url") or item.get("link") or "").strip()
318
+ if not url.startswith("http"):
319
+ continue
320
+ norm = _normalize_url(url)
321
+ if norm in seen:
322
+ continue
323
+ if not _domain_allowed(url, include_domains, exclude_domains):
324
+ continue
325
+ seen.add(norm)
326
+ snippet = (
327
+ item.get("markdown")
328
+ or item.get("description")
329
+ or item.get("snippet")
330
+ or item.get("content")
331
+ or ""
332
+ )
333
+ results.append(
334
+ WebSearchResult(
335
+ url=url,
336
+ title=(item.get("title") or url)[:300],
337
+ snippet=str(snippet)[:8000],
338
+ backend=self.name,
339
+ metadata={"has_markdown": bool(item.get("markdown"))},
340
+ )
341
+ )
342
+ if len(results) >= max_results:
343
+ break
344
+
345
+ logger.info("FirecrawlBackend: %d results for %r", len(results), query[:60])
346
+ return results
347
+
348
+
349
+ def get_configured_backends() -> list[SearchBackend]:
350
+ """Return enabled search backends from WEB_SEARCH_BACKENDS config."""
351
+ raw = getattr(cfg, "WEB_SEARCH_BACKENDS", ("ddgs",))
352
+ names = {n.strip().lower() for n in raw if n.strip()}
353
+ backends: list[SearchBackend] = []
354
+ if "firecrawl" in names:
355
+ fc = FirecrawlBackend()
356
+ if fc.enabled:
357
+ backends.append(fc)
358
+ else:
359
+ logger.debug("Firecrawl configured but FIRECRAWL_BASE_URL is empty — skipping")
360
+ if "ddgs" in names or "duckduckgo" in names:
361
+ backends.append(DuckDuckGoBackend())
362
+ if "searxng" in names:
363
+ sx = SearXNGBackend()
364
+ if sx.enabled:
365
+ backends.append(sx)
366
+ else:
367
+ logger.debug("SearXNG configured but SEARXNG_BASE_URL is empty — skipping")
368
+ return backends
@@ -0,0 +1,25 @@
1
+ """ServiceNow connector — Phase 8 stub."""
2
+ from __future__ import annotations
3
+
4
+ from datetime import datetime
5
+
6
+ from knowledge_base.config import cfg
7
+ from knowledge_base.connectors.base import BaseConnector
8
+
9
+
10
+ class ServiceNowConnector(BaseConnector):
11
+ name = "servicenow"
12
+ enabled = cfg.SERVICENOW_ENABLED
13
+ supports_sync = False
14
+
15
+ def connect(self) -> bool:
16
+ return self.enabled
17
+
18
+ def sync(self, since: datetime | None = None) -> list[dict]:
19
+ return []
20
+
21
+ def ingest_to_kg(self, items: list[dict]) -> int:
22
+ return 0
23
+
24
+ def ingest_to_rag(self, items: list[dict]) -> int:
25
+ return 0
@@ -0,0 +1,111 @@
1
+ """Connector sync scheduler with incremental state and daemon mode."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import logging
7
+ import sys
8
+ import time
9
+ from datetime import datetime, timezone
10
+
11
+ from knowledge_base.connectors.bitbucket_connector import BitbucketConnector
12
+ from knowledge_base.connectors.community_connector import CommunityConnector
13
+ from knowledge_base.connectors.confluence_connector import ConfluenceConnector
14
+ from knowledge_base.connectors.jira_connector import JiraConnector
15
+ from knowledge_base.connectors.kb_igel_connector import KbIgelConnector
16
+ from knowledge_base.connectors.serviceNow_connector import ServiceNowConnector
17
+ from knowledge_base.connectors.teamcity_connector import TeamCityConnector
18
+ from knowledge_base.connectors.testRail_connector import TestRailConnector
19
+ from knowledge_base.db.client import init_pool
20
+ from knowledge_base.db.connector_sync import get_last_sync, update_sync_state
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ CONNECTORS = {
25
+ "jira": JiraConnector,
26
+ "confluence": ConfluenceConnector,
27
+ "bitbucket": BitbucketConnector,
28
+ "teamcity": TeamCityConnector,
29
+ "testrail": TestRailConnector,
30
+ "servicenow": ServiceNowConnector,
31
+ "kb_igel": KbIgelConnector,
32
+ "community": CommunityConnector,
33
+ }
34
+
35
+
36
+ def sync_source(name: str, *, dry_run: bool = False) -> dict:
37
+ cls = CONNECTORS.get(name)
38
+ if not cls:
39
+ raise ValueError(f"Unknown source: {name}")
40
+ connector = cls()
41
+ if not connector.enabled:
42
+ return {"source": name, "status": "disabled", "items": 0}
43
+ if not getattr(connector, "supports_sync", True):
44
+ return {"source": name, "status": "not_implemented", "items": 0}
45
+
46
+ if not connector.connect():
47
+ update_sync_state(name, status="connection_failed", advance_timestamp=False)
48
+ return {"source": name, "status": "connection_failed", "items": 0}
49
+
50
+ since = get_last_sync(name)
51
+ items = connector.sync(since=since)
52
+ if dry_run:
53
+ return {"source": name, "status": "dry_run", "items": len(items), "since": str(since)}
54
+
55
+ kg_count = connector.ingest_to_kg(items)
56
+ rag_count = connector.ingest_to_rag(items)
57
+ update_sync_state(name, status="synced")
58
+ return {"source": name, "status": "synced", "items": len(items), "kg": kg_count, "rag": rag_count}
59
+
60
+
61
+ def sync_all(*, dry_run: bool = False) -> list[dict]:
62
+ results = []
63
+ for name in CONNECTORS:
64
+ try:
65
+ results.append(sync_source(name, dry_run=dry_run))
66
+ except Exception as e:
67
+ update_sync_state(name, status="error", advance_timestamp=False)
68
+ results.append({"source": name, "status": "error", "error": str(e)})
69
+ return results
70
+
71
+
72
+ def main() -> int:
73
+ logging.basicConfig(level=logging.INFO)
74
+ parser = argparse.ArgumentParser(description="IGEL KB connector sync scheduler")
75
+ parser.add_argument("--source", default="all", help="Connector name or 'all'")
76
+ parser.add_argument("--dry-run", action="store_true")
77
+ parser.add_argument("--daemon", action="store_true", help="Run sync loop")
78
+ parser.add_argument("--interval", type=int, default=3600, help="Daemon interval seconds")
79
+ args = parser.parse_args()
80
+
81
+ if args.interval <= 0:
82
+ parser.error("--interval must be a positive integer")
83
+
84
+ init_pool()
85
+
86
+ def _run_once():
87
+ if args.source == "all":
88
+ return sync_all(dry_run=args.dry_run)
89
+ return [sync_source(args.source, dry_run=args.dry_run)]
90
+
91
+ if args.daemon:
92
+ while True:
93
+ try:
94
+ rows = _run_once()
95
+ except Exception as e:
96
+ logger.exception("sync daemon cycle failed for source=%s: %s", args.source, e)
97
+ if args.source != "all" and args.source in CONNECTORS:
98
+ update_sync_state(args.source, status="error", advance_timestamp=False)
99
+ rows = [{"source": args.source, "status": "error", "error": str(e)}]
100
+ for r in rows:
101
+ print(r)
102
+ time.sleep(args.interval)
103
+ return 0
104
+
105
+ for r in _run_once():
106
+ print(r)
107
+ return 0
108
+
109
+
110
+ if __name__ == "__main__":
111
+ sys.exit(main())
@@ -0,0 +1,25 @@
1
+ """TeamCity connector — Phase 8 stub."""
2
+ from __future__ import annotations
3
+
4
+ from datetime import datetime
5
+
6
+ from knowledge_base.config import cfg
7
+ from knowledge_base.connectors.base import BaseConnector
8
+
9
+
10
+ class TeamCityConnector(BaseConnector):
11
+ name = "teamcity"
12
+ enabled = cfg.TEAMCITY_ENABLED
13
+ supports_sync = False
14
+
15
+ def connect(self) -> bool:
16
+ return self.enabled
17
+
18
+ def sync(self, since: datetime | None = None) -> list[dict]:
19
+ return []
20
+
21
+ def ingest_to_kg(self, items: list[dict]) -> int:
22
+ return 0
23
+
24
+ def ingest_to_rag(self, items: list[dict]) -> int:
25
+ return 0