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,189 @@
1
+ """
2
+ Unified query orchestrator — RAG + EKG + Web + MCP external sources.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import logging
8
+ import random
9
+ from dataclasses import dataclass, field
10
+ from time import perf_counter
11
+
12
+ from knowledge_base.config import cfg
13
+ from knowledge_base.connectors.query_bridge import ExternalContext, query_mcp_sources
14
+ from knowledge_base.retrieval.retriever import RetrievalBundle, retrieve_v3
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def _empty_bundle(query: str, *, reason: str) -> RetrievalBundle:
20
+ """Build a safe empty fallback bundle for timeout/error paths."""
21
+ from knowledge_base.retrieval.knowledge_graph import KGContext
22
+ from knowledge_base.retrieval.retrieval_debug import RetrievalDebugReport
23
+
24
+ return RetrievalBundle(
25
+ results=[],
26
+ kg_context=KGContext("", [], 0, 0),
27
+ image_block="",
28
+ image_hits=[],
29
+ debug=RetrievalDebugReport(
30
+ query=query,
31
+ query_classification=reason,
32
+ total_vector_candidates=0,
33
+ total_fts_candidates=0,
34
+ rrf_merged_count=0,
35
+ reranker_applied=False,
36
+ post_rerank_count=0,
37
+ kg_nodes_found=0,
38
+ kg_edges_traversed=0,
39
+ final_chunk_count=0,
40
+ total_tokens_used=0,
41
+ token_budget=int(getattr(cfg, "CONTEXT_TOKEN_BUDGET", 8000)),
42
+ traces=[],
43
+ timing_ms={},
44
+ ),
45
+ web_hits=[],
46
+ web_block="",
47
+ )
48
+
49
+
50
+ @dataclass
51
+ class UnifiedQueryResult:
52
+ rag: RetrievalBundle
53
+ web_block: str = ""
54
+ web_hits: list = field(default_factory=list)
55
+ mcp: ExternalContext = field(default_factory=ExternalContext)
56
+ sources_summary: dict = field(default_factory=dict)
57
+ timing_ms: dict = field(default_factory=dict)
58
+
59
+ @property
60
+ def kg_context(self):
61
+ return self.rag.kg_context
62
+
63
+ @property
64
+ def results(self):
65
+ return self.rag.results
66
+
67
+ @property
68
+ def debug(self):
69
+ return self.rag.debug
70
+
71
+ @property
72
+ def precedence_conflicts(self):
73
+ return self.rag.precedence_conflicts
74
+
75
+ @property
76
+ def precedence_attribution(self):
77
+ return self.rag.precedence_attribution
78
+
79
+ @property
80
+ def combined_context_block(self) -> str:
81
+ parts = []
82
+ if self.rag.kg_context.text_block:
83
+ parts.append(self.rag.kg_context.text_block.strip())
84
+ if self.rag.image_block:
85
+ parts.append(self.rag.image_block.strip())
86
+ if self.web_block:
87
+ parts.append(self.web_block.strip())
88
+ if self.mcp.block:
89
+ parts.append(self.mcp.block.strip())
90
+ return "\n\n".join(p for p in parts if p)
91
+
92
+
93
+ def _maybe_persist_trace(query: str, result: "UnifiedQueryResult") -> None:
94
+ rate = getattr(cfg, "RETRIEVAL_TRACE_SAMPLE_RATE", 0.0)
95
+ if rate <= 0 or random.random() > rate:
96
+ return
97
+ try:
98
+ from knowledge_base.core.request_context import get_request_id
99
+ from knowledge_base.db.client import get_conn
100
+ import json
101
+ with get_conn() as conn:
102
+ with conn.cursor() as cur:
103
+ cur.execute(
104
+ """
105
+ INSERT INTO retrieval_traces (request_id, query, sources_summary, debug_json, timing_ms)
106
+ VALUES (%s, %s, %s::jsonb, %s::jsonb, %s::jsonb)
107
+ """,
108
+ (
109
+ get_request_id(),
110
+ query[:2000],
111
+ json.dumps(result.sources_summary),
112
+ result.debug.to_json() if result.debug else "{}",
113
+ json.dumps(result.timing_ms),
114
+ ),
115
+ )
116
+ except Exception as exc:
117
+ logger.debug("Trace persistence skipped: %s", exc)
118
+
119
+
120
+ def unified_query(
121
+ query: str,
122
+ *,
123
+ top_k: int = 8,
124
+ product: str | None = None,
125
+ chunk_type: str | None = None,
126
+ ) -> UnifiedQueryResult:
127
+ """Orchestrate RAG + EKG + Web + MCP for every user query."""
128
+ timings: dict[str, float] = {}
129
+ timeout = max(1.0, float(getattr(cfg, "QUERY_TIMEOUT_SECONDS", 60)))
130
+ deadline = perf_counter() + timeout
131
+
132
+ def _rag():
133
+ t0 = perf_counter()
134
+ bundle = retrieve_v3(query, top_k=top_k, product=product, chunk_type=chunk_type)
135
+ timings["rag_kg_web_ms"] = (perf_counter() - t0) * 1000
136
+ return bundle
137
+
138
+ def _mcp(*, timeout_seconds: float | None = None):
139
+ t0 = perf_counter()
140
+ ctx = query_mcp_sources(
141
+ query,
142
+ max_hits=getattr(cfg, "WEB_MAX_PAGES", 3),
143
+ timeout_seconds=timeout_seconds,
144
+ )
145
+ timings["mcp_ms"] = (perf_counter() - t0) * 1000
146
+ return ctx
147
+
148
+ bundle = _empty_bundle(query, reason="init")
149
+ mcp = ExternalContext()
150
+ try:
151
+ bundle = _rag()
152
+ except Exception as exc:
153
+ logger.exception("RAG retrieval failed: %s", exc)
154
+ bundle = _empty_bundle(query, reason="error")
155
+
156
+ if getattr(cfg, "QUERY_EXTERNAL_ENABLED", True):
157
+ remaining = deadline - perf_counter()
158
+ if remaining <= 0:
159
+ logger.warning("MCP query skipped due exhausted unified timeout budget")
160
+ else:
161
+ try:
162
+ mcp = _mcp(timeout_seconds=max(0.05, remaining))
163
+ except Exception as exc:
164
+ logger.warning("MCP query failed: %s", exc)
165
+
166
+ web_block = getattr(bundle, "web_block", "") or ""
167
+ web_hits = list(getattr(bundle, "web_hits", []) or [])
168
+
169
+ sources_summary = {
170
+ "rag_chunks": len(bundle.results),
171
+ "kg_nodes": bundle.kg_context.node_count,
172
+ "kg_edges": bundle.kg_context.edge_count,
173
+ "web_hits": len(web_hits),
174
+ "mcp_hits": len(mcp.hits),
175
+ "mcp_sources": mcp.sources_queried,
176
+ "reranker": bundle.debug.reranker_applied,
177
+ "kg_backend": getattr(cfg, "KG_BACKEND", "postgres"),
178
+ }
179
+
180
+ result = UnifiedQueryResult(
181
+ rag=bundle,
182
+ web_block=web_block,
183
+ web_hits=web_hits,
184
+ mcp=mcp,
185
+ sources_summary=sources_summary,
186
+ timing_ms=timings,
187
+ )
188
+ _maybe_persist_trace(query, result)
189
+ return result
@@ -0,0 +1,337 @@
1
+ """Live web retrieval — confidence-gated Stage 2 search (Firecrawl / DuckDuckGo / SearXNG)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ import math
8
+ from dataclasses import dataclass, field
9
+ from time import perf_counter
10
+ from urllib.parse import urlparse
11
+
12
+ from knowledge_base.config import cfg
13
+ from knowledge_base.connectors.search_backends import get_configured_backends
14
+ from knowledge_base.connectors.web_fetcher import fetch_url, is_safe_fetch_target
15
+ from knowledge_base.parsers.html_parser import parse_html_page
16
+ from knowledge_base.retrieval.reranker import rerank_web_pages
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ @dataclass
22
+ class WebHit:
23
+ url: str
24
+ title: str
25
+ content: str
26
+ source_type: str
27
+ score: float = 0.5
28
+ metadata: dict = field(default_factory=dict)
29
+
30
+
31
+ def _parse_domain_list(raw: str | tuple[str, ...] | None) -> tuple[str, ...]:
32
+ if not raw:
33
+ return ()
34
+ if isinstance(raw, tuple):
35
+ return tuple(s.strip() for s in raw if s and str(s).strip())
36
+ return tuple(s.strip() for s in str(raw).split(",") if s.strip())
37
+
38
+
39
+ def _domains_from_sources() -> tuple[str, ...]:
40
+ """Resolve WEB_SOURCES aliases into host allowlist entries."""
41
+ raw_sources = getattr(cfg, "WEB_SOURCES", ())
42
+ if isinstance(raw_sources, str):
43
+ sources = [s.strip().lower() for s in raw_sources.split(",") if s.strip()]
44
+ else:
45
+ sources = [str(s).strip().lower() for s in raw_sources if str(s).strip()]
46
+
47
+ domains: list[str] = []
48
+ for source in sources:
49
+ if source == "kb_igel" and getattr(cfg, "KB_IGEL_ENABLED", True):
50
+ host = urlparse(str(getattr(cfg, "KB_IGEL_BASE_URL", "")).strip()).netloc.lower()
51
+ if host:
52
+ domains.append(host)
53
+ elif source == "community" and getattr(cfg, "COMMUNITY_ENABLED", True):
54
+ host = urlparse(str(getattr(cfg, "COMMUNITY_BASE_URL", "")).strip()).netloc.lower()
55
+ if host:
56
+ domains.append(host)
57
+
58
+ # Preserve order while deduplicating.
59
+ seen: set[str] = set()
60
+ out: list[str] = []
61
+ for d in domains:
62
+ if d not in seen:
63
+ out.append(d)
64
+ seen.add(d)
65
+ return tuple(out)
66
+
67
+
68
+ def _fetch_page_content_httpx(url: str) -> tuple[str, str]:
69
+ """Fallback: httpx GET + HTML parse."""
70
+ cache_ttl = int(getattr(cfg, "WEB_CACHE_TTL_SECONDS", 3600))
71
+ result = fetch_url(url, cache_ttl=cache_ttl, strip_promoted=True)
72
+ if result.status_code != 200 or not result.html:
73
+ return url, ""
74
+
75
+ parsed = parse_html_page(result.html, url, source_type="web_search")
76
+ title = parsed.title or url
77
+ content = parsed.raw_text or ""
78
+ return title, content
79
+
80
+
81
+ def _extract_crawl4ai_markdown(result: object) -> str:
82
+ """Normalize markdown across Crawl4AI versions."""
83
+ for attr in ("markdown", "fit_markdown", "cleaned_html"):
84
+ val = getattr(result, attr, None)
85
+ if isinstance(val, str) and val.strip():
86
+ return val.strip()
87
+ md_v2 = getattr(result, "markdown_v2", None)
88
+ if md_v2 is not None:
89
+ raw = getattr(md_v2, "raw_markdown", None) or getattr(md_v2, "fit_markdown", None)
90
+ if isinstance(raw, str) and raw.strip():
91
+ return raw.strip()
92
+ return ""
93
+
94
+
95
+ async def _fetch_page_content_crawl4ai(url: str, query: str) -> tuple[str, str]:
96
+ """Fetch URL with Crawl4AI Playwright crawler (LLM-ready markdown)."""
97
+ try:
98
+ from crawl4ai import AsyncWebCrawler
99
+ except ImportError as e:
100
+ raise ImportError("crawl4ai not installed") from e
101
+
102
+ timeout = int(getattr(cfg, "CRAWL4AI_TIMEOUT", 30))
103
+ run_config = None
104
+ try:
105
+ from crawl4ai import CrawlerRunConfig
106
+
107
+ filter_kwargs: dict = {}
108
+ if query.strip():
109
+ try:
110
+ from crawl4ai.content_filter_strategy import BM25ContentFilter
111
+
112
+ filter_kwargs["content_filter"] = BM25ContentFilter(user_query=query)
113
+ except ImportError:
114
+ try:
115
+ from crawl4ai.content_filter_strategy import PruningContentFilter
116
+
117
+ filter_kwargs["content_filter"] = PruningContentFilter()
118
+ except ImportError:
119
+ pass
120
+ run_config = CrawlerRunConfig(**filter_kwargs) if filter_kwargs else CrawlerRunConfig()
121
+ except ImportError:
122
+ run_config = None
123
+
124
+ async with AsyncWebCrawler(verbose=False) as crawler:
125
+ if run_config is not None:
126
+ result = await asyncio.wait_for(crawler.arun(url=url, config=run_config), timeout=timeout)
127
+ else:
128
+ result = await asyncio.wait_for(crawler.arun(url=url), timeout=timeout)
129
+
130
+ if not result or not getattr(result, "success", True):
131
+ return url, ""
132
+
133
+ title = getattr(result, "title", None) or url
134
+ markdown = _extract_crawl4ai_markdown(result)
135
+ return title, markdown
136
+
137
+
138
+ def _fetch_page_content_playwright(url: str) -> tuple[str, str]:
139
+ from knowledge_base.connectors.playwright_fetcher import fetch_page_playwright, playwright_available
140
+
141
+ if not playwright_available():
142
+ raise ImportError("playwright not installed")
143
+ timeout_ms = int(getattr(cfg, "CRAWL4AI_TIMEOUT", 30)) * 1000
144
+ return fetch_page_playwright(url, timeout_ms=timeout_ms)
145
+
146
+
147
+ def _fetch_page_content(url: str, query: str = "") -> tuple[str, str]:
148
+ """Fetch page content — Crawl4AI → Playwright → httpx fallback.
149
+
150
+ Crawl4AI uses an async crawler. To avoid conflicting with the running
151
+ uvicorn/FastAPI event loop, the coroutine is executed in a fresh thread
152
+ (which has no pre-existing loop) via ThreadPoolExecutor.
153
+ Playwright fallback is intentionally outside the CRAWL4AI_ENABLED guard
154
+ so it remains available when Crawl4AI is disabled or fails.
155
+ """
156
+ if getattr(cfg, "CRAWL4AI_ENABLED", True):
157
+ try:
158
+ import concurrent.futures
159
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
160
+ return pool.submit(asyncio.run, _fetch_page_content_crawl4ai(url, query)).result()
161
+ except ImportError:
162
+ logger.debug("crawl4ai unavailable — trying Playwright fetcher")
163
+ except Exception as e:
164
+ logger.warning("crawl4ai fetch failed for %s: %s", url, e)
165
+
166
+ # Playwright fallback — runs independently of CRAWL4AI_ENABLED
167
+ try:
168
+ return _fetch_page_content_playwright(url)
169
+ except ImportError:
170
+ logger.debug("playwright unavailable — httpx fallback for %s", url)
171
+ except Exception as e:
172
+ logger.warning("playwright fetch failed for %s: %s — httpx fallback", url, e)
173
+
174
+ return _fetch_page_content_httpx(url)
175
+
176
+
177
+ def retrieve_web_context(
178
+ query: str,
179
+ *,
180
+ max_pages: int | None = None,
181
+ local_top_score: float | None = None,
182
+ ) -> list[WebHit]:
183
+ """
184
+ Stage 2 live web search — only runs when local RAG confidence is below threshold.
185
+
186
+ Backends (WEB_SEARCH_BACKENDS):
187
+ - firecrawl → self-hosted Firecrawl search (primary)
188
+ - ddgs → DuckDuckGo organic search (fallback)
189
+ - searxng → Self-hosted SearXNG (optional)
190
+ """
191
+ if not getattr(cfg, "WEB_RETRIEVAL_ENABLED", False):
192
+ return []
193
+ if not query.strip():
194
+ return []
195
+
196
+ threshold = float(getattr(cfg, "CONFIDENCE_THRESHOLD", 0.75))
197
+ if local_top_score is not None and local_top_score >= threshold:
198
+ logger.info(
199
+ "confidence gate: local top score %.3f >= %.3f — skipping web search for %r",
200
+ local_top_score,
201
+ threshold,
202
+ query[:60],
203
+ )
204
+ return []
205
+
206
+ if local_top_score is not None:
207
+ logger.info(
208
+ "confidence gate: local top score %.3f < %.3f — escalating to web search for %r",
209
+ local_top_score,
210
+ threshold,
211
+ query[:60],
212
+ )
213
+ else:
214
+ logger.info("confidence gate: no local score — running web search for %r", query[:60])
215
+
216
+ max_pages = max_pages or int(getattr(cfg, "WEB_MAX_PAGES", 5))
217
+ min_score = float(getattr(cfg, "WEB_MIN_SCORE", 0.15))
218
+ include_domains = _parse_domain_list(getattr(cfg, "WEB_SEARCH_INCLUDE_DOMAINS", ()))
219
+ exclude_domains = _parse_domain_list(getattr(cfg, "WEB_SEARCH_EXCLUDE_DOMAINS", ()))
220
+ include_from_sources = _domains_from_sources()
221
+ if include_from_sources:
222
+ include_domains = tuple(dict.fromkeys((*include_domains, *include_from_sources)))
223
+
224
+ backends = get_configured_backends()
225
+ if not backends:
226
+ logger.warning("no web search backends configured (WEB_SEARCH_BACKENDS)")
227
+ return []
228
+
229
+ t0 = perf_counter()
230
+ seen_urls: set[str] = set()
231
+ candidates: list[tuple[str, str, str, str]] = [] # url, title, content, backend
232
+
233
+ per_backend = max(1, math.ceil(max_pages / max(len(backends), 1)))
234
+ for backend in backends:
235
+ try:
236
+ search_results = backend.search(
237
+ query,
238
+ max_results=per_backend,
239
+ include_domains=include_domains or None,
240
+ exclude_domains=exclude_domains or None,
241
+ )
242
+ except Exception as e:
243
+ logger.warning("web backend %s search failed: %s", backend.name, e)
244
+ continue
245
+
246
+ for sr in search_results:
247
+ if not is_safe_fetch_target(sr.url):
248
+ logger.warning("skipping unsafe web target from backend %s: %s", backend.name, sr.url)
249
+ continue
250
+ norm = sr.url.rstrip("/").lower()
251
+ if norm in seen_urls:
252
+ continue
253
+ seen_urls.add(norm)
254
+
255
+ # Firecrawl may return full markdown in snippet — skip re-fetch if substantial.
256
+ if sr.snippet and len(sr.snippet.strip()) >= 400 and sr.metadata.get("has_markdown"):
257
+ title = sr.title or sr.url
258
+ content = sr.snippet[:12000]
259
+ else:
260
+ title, content = _fetch_page_content(sr.url, query)
261
+ if not content and sr.snippet:
262
+ content = sr.snippet
263
+ if not title:
264
+ title = sr.title or sr.url
265
+
266
+ if len(content.strip()) < 40 and not sr.snippet:
267
+ continue
268
+
269
+ candidates.append((sr.url, title, content[:12000], backend.name))
270
+
271
+ if not candidates:
272
+ logger.info("retrieve_web_context('%s') → 0 candidates", query[:50])
273
+ return []
274
+
275
+ pages_for_rerank = [(url, title, content) for url, title, content, _ in candidates]
276
+ ranked = rerank_web_pages(query, pages_for_rerank)
277
+ score_by_url = {url: score for url, score in ranked}
278
+
279
+ hits: list[WebHit] = []
280
+ backend_by_url = {url: backend for url, _, _, backend in candidates}
281
+ for url, title, content, backend in candidates:
282
+ score = float(score_by_url.get(url, 0.0))
283
+ if score < min_score:
284
+ continue
285
+ hits.append(
286
+ WebHit(
287
+ url=url,
288
+ title=title,
289
+ content=content,
290
+ source_type="web_search",
291
+ score=score,
292
+ metadata={
293
+ "source_type": "web_search",
294
+ "canonical_url": url,
295
+ "web_source": backend_by_url.get(url, backend),
296
+ "confidence_gate": "escalated",
297
+ "local_top_score": local_top_score,
298
+ },
299
+ )
300
+ )
301
+
302
+ hits.sort(key=lambda h: h.score, reverse=True)
303
+ hits = hits[:max_pages]
304
+
305
+ elapsed = (perf_counter() - t0) * 1000
306
+ logger.info(
307
+ "retrieve_web_context('%s') → %d hits in %.0fms (backends=%s, gate=open)",
308
+ query[:50],
309
+ len(hits),
310
+ elapsed,
311
+ [b.name for b in backends],
312
+ )
313
+ return hits
314
+
315
+
316
+ def format_web_block(hits: list[WebHit], *, max_chars: int = 6000) -> str:
317
+ """Format web hits for LLM context injection."""
318
+ if not hits:
319
+ return ""
320
+ lines = ["—— Live web sources (Firecrawl / organic search) ——"]
321
+ used = 0
322
+ for hit in hits:
323
+ backend = hit.metadata.get("web_source", hit.source_type)
324
+ block = (
325
+ f"\n### [{backend}] {hit.title}\n"
326
+ f"URL: {hit.url}\n"
327
+ f"Relevance: {hit.score:.2f}\n\n"
328
+ f"{hit.content[:4000]}"
329
+ )
330
+ if used + len(block) > max_chars:
331
+ remain = max_chars - used - 80
332
+ if remain > 200:
333
+ lines.append(block[:remain] + "\n[...truncated]")
334
+ break
335
+ lines.append(block)
336
+ used += len(block)
337
+ return "\n".join(lines)
@@ -0,0 +1 @@
1
+ """Post-generation validators for test case fidelity."""
@@ -0,0 +1,88 @@
1
+ """
2
+ Post-generation checks: preserve gold testcase step count and avoid hollow expected results.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import re
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from knowledge_base.generation.manual_tc_parser import ParsedStep
11
+
12
+
13
+ _VAGUE_PHRASES = (
14
+ "works as expected",
15
+ "verify success",
16
+ "test passes",
17
+ "everything works",
18
+ "as expected.",
19
+ "proper behavior",
20
+ "configuration is correct",
21
+ )
22
+
23
+
24
+ def count_pytest_steps(python_code: str) -> int:
25
+ """Count numbered step functions like `def TC001_step1` or `def QCL_4851_step2`."""
26
+ if not python_code:
27
+ return 0
28
+ # Primary: _stepN( before cleanup
29
+ n = len(re.findall(r"def\s+[A-Za-z0-9_]+_step\d+\s*\(", python_code))
30
+ if n:
31
+ return n
32
+ return len(re.findall(r"@allure\.step\(", python_code))
33
+
34
+
35
+ def validate_transform_fidelity(
36
+ parsed_steps: list,
37
+ python_code: str | None,
38
+ markdown: str | None,
39
+ ) -> list[str]:
40
+ """
41
+ For TRANSFORM mode: core steps in generated Python should match parsed manual step count.
42
+ Allows one extra step for cleanup pattern.
43
+ """
44
+ warnings: list[str] = []
45
+ if not parsed_steps:
46
+ return warnings
47
+
48
+ target = len(parsed_steps)
49
+ if python_code:
50
+ got = count_pytest_steps(python_code)
51
+ if got == 0:
52
+ warnings.append("FIDELITY: No stepN functions detected in generated Python.")
53
+ elif got < target:
54
+ warnings.append(
55
+ f"FIDELITY: Generated {got} automation steps but manual TC has {target} — "
56
+ "possible dropped steps (review required)."
57
+ )
58
+ elif got > target + 1:
59
+ warnings.append(
60
+ f"FIDELITY: Generated {got} steps vs manual {target} — "
61
+ "possible invented steps (only +1 allowed for cleanup)."
62
+ )
63
+
64
+ if markdown:
65
+ low = markdown.lower()
66
+ for phrase in _VAGUE_PHRASES:
67
+ if phrase in low:
68
+ warnings.append(f"FIDELITY: Markdown contains vague phrase: {phrase!r}")
69
+ break
70
+
71
+ return warnings
72
+
73
+
74
+ def validate_expand_preservation(
75
+ parsed_steps: list,
76
+ markdown: str | None,
77
+ ) -> list[str]:
78
+ """Heuristic: original step titles should still appear in expanded markdown."""
79
+ out: list[str] = []
80
+ if not parsed_steps or not markdown:
81
+ return out
82
+ for s in parsed_steps[:20]:
83
+ title = (s.step_title or s.test_data or "")[:40].strip()
84
+ if len(title) > 12 and title.lower() not in markdown.lower():
85
+ out.append(
86
+ f"FIDELITY_EXPAND: Original step fragment not found in output: {title[:60]!r}…"
87
+ )
88
+ return out[:5]
File without changes