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,778 @@
1
+ """
2
+ IGEL Test Case Generation — Always-Running REST API
3
+ =====================================================
4
+ Start:
5
+ uvicorn knowledge_base.api.server:app --host 0.0.0.0 --port 8000
6
+
7
+ Endpoints:
8
+ POST /query — Ask any test-case-related question; returns detailed KB-grounded steps
9
+ POST /generate — Full test case generation (markdown + python) from a topic and/or pasted manual TC
10
+ POST /convert — Convert a gold/partial manual TC to pytest (faithful transform/expand pipeline)
11
+ POST /feedback — Submit feedback on generated output for curation
12
+ POST /generate/v2 — Agentic generation (LangGraph multi-agent pipeline)
13
+ POST /rca — Root cause analysis for test failures (Phase 7)
14
+ POST /heal — Script healing for flaky tests (Phase 7)
15
+ POST /impact-analysis — Regression impact from change set (Phase 7)
16
+ GET /health — DB, KG, and embedding readiness check
17
+ GET /stats — Current KB statistics (docs, chunks, KG nodes/edges)
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+ import tempfile
23
+ import time
24
+ from contextlib import asynccontextmanager
25
+ from pathlib import Path
26
+ from typing import Any, Dict, List, Optional, Tuple
27
+ from uuid import uuid4
28
+
29
+ import anyio
30
+ from fastapi import FastAPI, HTTPException, Depends, Header, Request
31
+ from pydantic import BaseModel, Field, model_validator, field_validator
32
+
33
+ from knowledge_base.config import cfg
34
+ from knowledge_base.db.client import init_pool, test_connection, close_pool, get_conn
35
+ from knowledge_base.db.schema import create_schema, get_stats, ensure_knowledge_graph_tables
36
+ from knowledge_base.db.feedback_schema import ensure_feedback_tables
37
+ from knowledge_base.feedback.handler import capture_feedback
38
+ from knowledge_base.retrieval.unified_query import unified_query
39
+ from knowledge_base.core.logging_setup import configure_logging
40
+ from knowledge_base.core.metrics import metrics, Timer
41
+ from knowledge_base.core.request_context import (
42
+ api_key_scope_var,
43
+ get_request_id,
44
+ hash_api_key,
45
+ resolve_scope,
46
+ )
47
+ from knowledge_base.api.middleware import RequestTracingMiddleware, RateLimitMiddleware
48
+ from knowledge_base.generation.prompts import (
49
+ PromptContext,
50
+ build_system_prompt,
51
+ build_markdown_user_prompt,
52
+ format_kb_retrieval_references_markdown,
53
+ kb_trace_lines,
54
+ )
55
+ from knowledge_base.generation.test_generator import (
56
+ generate_test_cases,
57
+ _infer_feature_area,
58
+ _call_gpt,
59
+ )
60
+
61
+ logging.basicConfig(level=getattr(logging, cfg.LOG_LEVEL, "INFO"), format="%(levelname)s %(message)s")
62
+ configure_logging(level=cfg.LOG_LEVEL, json_logs=cfg.JSON_LOGS)
63
+ logger = logging.getLogger(__name__)
64
+ _THREAD_LIMITER = anyio.CapacityLimiter(max(2, int(getattr(cfg, "ASYNC_THREAD_LIMIT", 16))))
65
+
66
+
67
+ async def _run_blocking(fn):
68
+ """Run blocking call in bounded worker pool and abandon await on cancellation."""
69
+ try:
70
+ return await anyio.to_thread.run_sync(fn, abandon_on_cancel=True, limiter=_THREAD_LIMITER)
71
+ except TypeError:
72
+ # Backward compatibility with older AnyIO versions.
73
+ return await anyio.to_thread.run_sync(fn, limiter=_THREAD_LIMITER)
74
+
75
+
76
+ # ── Lifespan: DB pool + schema verification on startup ────────────────────────
77
+
78
+ @asynccontextmanager
79
+ async def lifespan(app: FastAPI):
80
+ logger.info("Starting IGEL Test Generation Server...")
81
+ logger.info(" DB: %s:%s/%s", cfg.DB_HOST, cfg.DB_PORT, cfg.DB_NAME)
82
+ logger.info(" LLM: %s @ %s", cfg.AZURE_DEPLOYMENT, cfg.AZURE_ENDPOINT[:40])
83
+ logger.info(" KG enabled: %s", cfg.KG_ENABLED)
84
+
85
+ init_pool()
86
+ try:
87
+ if not test_connection():
88
+ raise RuntimeError("Cannot connect to PostgreSQL — check knowledge_base/.env")
89
+
90
+ create_schema()
91
+ ensure_knowledge_graph_tables()
92
+ ensure_feedback_tables()
93
+
94
+ stats = get_stats()
95
+ logger.info(
96
+ " KB ready: %d docs, %d parent chunks, %d child chunks, %d KG nodes, %d KG edges",
97
+ stats["documents"], stats["parent_chunks"], stats["chunks"],
98
+ stats.get("kg_nodes", 0), stats.get("kg_edges", 0),
99
+ )
100
+ if stats["documents"] == 0:
101
+ logger.warning(" KB is EMPTY — run: python -m knowledge_base.ingestion.ingest")
102
+ orphans = stats.get("kg_orphan_parents (no KG entity)", 0)
103
+ if orphans > 0:
104
+ logger.warning(" %d parent chunks have no KG entity — run: python -m knowledge_base.ingestion.ingest --enrich-orphans", orphans)
105
+
106
+ if cfg.RERANKER_BACKEND == "local":
107
+ try:
108
+ from knowledge_base.retrieval.reranker import warm_start_reranker
109
+ warm_start_reranker()
110
+ logger.info(" Reranker model pre-loaded")
111
+ except Exception as exc:
112
+ logger.warning(" Reranker warm start failed: %s", exc)
113
+
114
+ yield
115
+ finally:
116
+ close_pool()
117
+ logger.info("Server shut down.")
118
+
119
+
120
+ app = FastAPI(
121
+ title="IGEL Test Case Generation API",
122
+ description="Always-running service: RAG + Knowledge Graph in sync. "
123
+ "Ask test-case questions or generate full executable test cases from the IGEL KB.",
124
+ version="1.0.0",
125
+ lifespan=lifespan,
126
+ )
127
+ app.add_middleware(RateLimitMiddleware)
128
+ app.add_middleware(RequestTracingMiddleware)
129
+
130
+
131
+ # ── Request/Response Models ───────────────────────────────────────────────────
132
+
133
+ class QueryRequest(BaseModel):
134
+ question: str = Field(..., max_length=2000, description="Natural language question about IGEL test cases")
135
+ product: Optional[str] = Field(None, description="Filter by product: UMS, IGEL OS, COSMOS, ICG, IMI")
136
+ top_k: int = Field(8, ge=1, le=20, description="Number of KB chunks to retrieve")
137
+
138
+ class QueryResponse(BaseModel):
139
+ answer: str
140
+ kb_sources: List[str]
141
+ kg_entities_used: int
142
+ retrieval_chunks: int
143
+ web_hits: int = 0
144
+ mcp_hits: int = 0
145
+ sources_queried: List[str] = Field(default_factory=list)
146
+ tokens_used: int
147
+ generation_time_sec: float
148
+
149
+ class GenerateRequest(BaseModel):
150
+ topic: str = Field(
151
+ "",
152
+ description="Test case topic or short label. May be omitted if source_tc is provided.",
153
+ )
154
+ product: Optional[str] = Field(None, description="Filter by product")
155
+ top_k: int = Field(8, ge=1, le=20)
156
+ output_format: str = Field("both", description="md, py, or both")
157
+ test_id: Optional[str] = Field(None, description="Custom test ID like TC015")
158
+ feature_area: Optional[str] = Field(None, description="Allure feature group")
159
+ source_tc: Optional[str] = Field(
160
+ None,
161
+ description="Optional pasted official manual TC (CSV / JIRA export). Enables transform or expand mode.",
162
+ )
163
+ detail_level: Optional[str] = Field(
164
+ None,
165
+ description="concise | detailed | auto — overrides default detail / classification hints.",
166
+ )
167
+ generation_mode: Optional[str] = Field(
168
+ None,
169
+ description="pipeline | agentic — A/B generation path (default from config).",
170
+ )
171
+
172
+ @model_validator(mode="after")
173
+ def _require_topic_or_source(self):
174
+ if not (self.topic or "").strip() and not (self.source_tc or "").strip():
175
+ raise ValueError("Provide a non-empty `topic` and/or `source_tc`.")
176
+ return self
177
+
178
+ @field_validator("source_tc")
179
+ @classmethod
180
+ def _limit_source_tc(cls, v: Optional[str]) -> Optional[str]:
181
+ if v and len(v) > 50_000:
182
+ raise ValueError("source_tc exceeds 50KB limit")
183
+ return v
184
+
185
+
186
+ class ConvertRequest(BaseModel):
187
+ """Dedicated body for gold / partial manual TC → pytest (no topic-only synthesis)."""
188
+
189
+ source_tc: str = Field(..., max_length=50000, description="Full manual test case text (structured steps).")
190
+ topic: str = Field(
191
+ "",
192
+ description="Short label or JIRA key for naming (defaults from first line of source_tc).",
193
+ )
194
+ product: Optional[str] = Field(None)
195
+ top_k: int = Field(8, ge=1, le=20)
196
+ output_format: str = Field("both", description="md, py, or both")
197
+ test_id: Optional[str] = Field(None)
198
+ feature_area: Optional[str] = Field(None)
199
+ detail_level: Optional[str] = Field(None, description="concise | detailed | auto")
200
+
201
+ class GenerateResponse(BaseModel):
202
+ test_id: str
203
+ topic: str
204
+ feature_area: str
205
+ markdown_content: Optional[str]
206
+ python_content: Optional[str]
207
+ kb_sources: List[str]
208
+ kb_chunk_count: int
209
+ tokens_used: int
210
+ generation_time_sec: float
211
+ syntax_valid: bool
212
+ warnings: List[str]
213
+ traceability_table: str
214
+ confidence: Optional[dict] = None
215
+ precedence_conflicts: List[dict] = Field(default_factory=list)
216
+
217
+
218
+ class FeedbackRequest(BaseModel):
219
+ query: str = Field(..., max_length=2000, description="Original query or topic used for generation")
220
+ generated_output: dict = Field(..., description="Full generation payload (markdown, python, etc.)")
221
+ feedback_type: str = Field(..., description="rating | correction | edit | rejection | execution_failure")
222
+ feedback_data: dict = Field(default_factory=dict, description="Structured feedback payload")
223
+ user_id: Optional[str] = None
224
+ session_id: Optional[str] = None
225
+ generation_metadata: Optional[dict] = None
226
+ source_context: Optional[dict] = None
227
+
228
+
229
+ class FeedbackResponse(BaseModel):
230
+ feedback_id: str
231
+ status: str = "pending"
232
+ message: str = "Feedback captured for curation"
233
+
234
+ class HealthResponse(BaseModel):
235
+ status: str
236
+ db_connected: bool
237
+ documents: int
238
+ chunks: int
239
+ kg_nodes: int
240
+ kg_edges: int
241
+ kg_orphan_parents: int
242
+ kg_backend: str = "postgres"
243
+ neo4j: Optional[dict] = None
244
+ cognee: Optional[dict] = None
245
+ graphiti: Optional[dict] = None
246
+ phase: str = "phase1"
247
+
248
+
249
+ class AgenticGenerateRequest(BaseModel):
250
+ query: str = Field(..., description="Test case topic or scenario")
251
+ product: Optional[str] = None
252
+ top_k: int = Field(8, ge=1, le=20)
253
+ output_format: str = Field("both", description="md, py, or both")
254
+ test_id: Optional[str] = None
255
+
256
+
257
+ class AgenticGenerateResponse(BaseModel):
258
+ test_id: str
259
+ query: str
260
+ feature_area: str
261
+ markdown_content: Optional[str]
262
+ python_content: Optional[str]
263
+ confidence: Optional[dict] = None
264
+ sources_used: List[str] = Field(default_factory=list)
265
+ agent_trace: List[dict] = Field(default_factory=list)
266
+ validation_errors: List[str] = Field(default_factory=list)
267
+ validation_warnings: List[str] = Field(default_factory=list)
268
+ warnings: List[str] = Field(default_factory=list)
269
+
270
+
271
+ class RCARequest(BaseModel):
272
+ test_id: str
273
+ failure_log: str = ""
274
+
275
+
276
+ class HealRequest(BaseModel):
277
+ test_id: str
278
+ flaky_history: List[dict] = Field(default_factory=list)
279
+
280
+
281
+ class ImpactAnalysisRequest(BaseModel):
282
+ changed_files: List[str] = Field(..., min_length=1)
283
+
284
+
285
+ # ── Auth middleware (Phase 9) ─────────────────────────────────────────────────
286
+
287
+ async def verify_api_key(x_api_key: Optional[str] = Header(default=None, alias="X-API-Key")):
288
+ if not cfg.API_KEY_REQUIRED:
289
+ api_key_scope_var.set("admin")
290
+ return
291
+ scope = resolve_scope(x_api_key, cfg.api_key_map, cfg.API_KEY)
292
+ if scope == "invalid":
293
+ metrics.inc("auth_failures_total")
294
+ raise HTTPException(status_code=401, detail="Invalid or missing API key")
295
+ api_key_scope_var.set(scope)
296
+
297
+
298
+ def require_scope(*allowed: str):
299
+ async def _check(_: None = Depends(verify_api_key)):
300
+ scope = api_key_scope_var.get()
301
+ if scope == "admin" or scope in allowed:
302
+ return
303
+ raise HTTPException(status_code=403, detail=f"Insufficient scope: need one of {allowed}")
304
+ return _check
305
+
306
+
307
+ # ── Endpoints ─────────────────────────────────────────────────────────────────
308
+
309
+ @app.get("/metrics")
310
+ async def prometheus_metrics():
311
+ from fastapi.responses import PlainTextResponse
312
+ return PlainTextResponse(metrics.render_prometheus(), media_type="text/plain; version=0.0.4")
313
+
314
+
315
+ @app.get("/health", response_model=HealthResponse)
316
+ async def health():
317
+ """Check system readiness: DB, KB population, KG health."""
318
+ try:
319
+ ok = test_connection()
320
+ except Exception:
321
+ ok = False
322
+ if not ok:
323
+ raise HTTPException(503, detail="Database not reachable")
324
+
325
+ stats = get_stats()
326
+ orphans = stats.get("kg_orphan_parents (no KG entity)", 0)
327
+ status = "healthy"
328
+ if stats["documents"] == 0:
329
+ status = "empty_kb"
330
+ elif stats.get("kg_nodes", 0) == 0:
331
+ status = "kg_empty"
332
+
333
+ reranker_ok = True
334
+ try:
335
+ from knowledge_base.retrieval.reranker import is_reranker_loaded
336
+ reranker_ok = is_reranker_loaded()
337
+ except Exception:
338
+ reranker_ok = False
339
+ if not reranker_ok and cfg.RERANKER_BACKEND == "local":
340
+ status = "degraded" if status == "healthy" else status
341
+
342
+ redis_ok = None
343
+ try:
344
+ from knowledge_base.core.cache import _redis
345
+ r = _redis()
346
+ redis_ok = r is not None and r.ping()
347
+ except Exception:
348
+ redis_ok = False
349
+
350
+ return HealthResponse(
351
+ status=status,
352
+ db_connected=True,
353
+ documents=stats["documents"],
354
+ chunks=stats["chunks"],
355
+ kg_nodes=stats.get("kg_nodes", 0),
356
+ kg_edges=stats.get("kg_edges", 0),
357
+ kg_orphan_parents=orphans,
358
+ kg_backend=cfg.KG_BACKEND,
359
+ neo4j=_component_health("neo4j"),
360
+ cognee=_component_health("cognee"),
361
+ graphiti=_component_health("graphiti"),
362
+ phase=_current_phase(),
363
+ )
364
+
365
+
366
+ @app.get("/stats")
367
+ async def stats():
368
+ """Full KB statistics."""
369
+ return get_stats()
370
+
371
+
372
+ @app.post("/query", response_model=QueryResponse, dependencies=[Depends(require_scope("read", "write", "admin"))])
373
+ async def query_test_steps(req: QueryRequest):
374
+ """
375
+ Ask any question related to IGEL test cases.
376
+ Returns detailed, executable test steps grounded in the Knowledge Base.
377
+ The response uses real registry keys, TC Setup paths, CLI commands from the DB.
378
+ """
379
+ if not req.question.strip():
380
+ raise HTTPException(400, detail="question cannot be empty")
381
+
382
+ t0 = time.time()
383
+
384
+ retrieval_timeout = max(1.0, float(getattr(cfg, "QUERY_TIMEOUT_SECONDS", 60)))
385
+ try:
386
+ with anyio.fail_after(retrieval_timeout):
387
+ uq = await _run_blocking(
388
+ lambda: unified_query(query=req.question, top_k=req.top_k, product=req.product)
389
+ )
390
+ except TimeoutError as exc:
391
+ raise HTTPException(
392
+ status_code=504,
393
+ detail=f"Retrieval timed out after {retrieval_timeout:.0f}s",
394
+ ) from exc
395
+ if not uq.results and not uq.web_hits and not uq.mcp.hits:
396
+ raise HTTPException(
397
+ 404,
398
+ detail=f"No KB content found for '{req.question}'. "
399
+ "Ensure documents are ingested and topic is specific enough.",
400
+ )
401
+
402
+ feature = _infer_feature_area(req.question)
403
+ ctx = PromptContext(
404
+ topic=req.question,
405
+ test_id="QUERY",
406
+ feature_area=feature,
407
+ product=req.product,
408
+ context_chunks=uq.results,
409
+ token_budget=int(cfg.CONTEXT_TOKEN_BUDGET),
410
+ kg_context_block=uq.kg_context.text_block,
411
+ image_context_block=uq.rag.image_block,
412
+ web_context_block=uq.web_block,
413
+ mcp_context_block=uq.mcp.block,
414
+ skip_assemble_window=True,
415
+ )
416
+
417
+ system_prompt = build_system_prompt() + _QUERY_ADDENDUM
418
+ user_prompt = build_markdown_user_prompt(ctx)
419
+
420
+ generation_timeout = max(1.0, float(getattr(cfg, "QUERY_GENERATION_TIMEOUT_SECONDS", 90)))
421
+ try:
422
+ with anyio.fail_after(generation_timeout):
423
+ answer = await _run_blocking(
424
+ lambda: _call_gpt(system_prompt, user_prompt, max_tokens=4096, temperature=0.2)
425
+ )
426
+ except TimeoutError as exc:
427
+ raise HTTPException(
428
+ status_code=504,
429
+ detail=f"Generation timed out after {generation_timeout:.0f}s",
430
+ ) from exc
431
+ elapsed = round(time.time() - t0, 1)
432
+
433
+ source_lines = kb_trace_lines(uq.results)
434
+ for i, hit in enumerate(uq.web_hits, start=1):
435
+ backend = hit.metadata.get("web_source", hit.source_type) if getattr(hit, "metadata", None) else hit.source_type
436
+ source_lines.append(f"[WEB_REF {i}] source={backend} | title={hit.title} | url={hit.url}")
437
+ for i, hit in enumerate(uq.mcp.hits, start=1):
438
+ source_lines.append(f"[MCP_REF {i}] source={hit.source} | title={hit.title} | url={hit.url}")
439
+
440
+ return QueryResponse(
441
+ answer=answer,
442
+ kb_sources=source_lines,
443
+ kg_entities_used=uq.kg_context.node_count,
444
+ retrieval_chunks=len(uq.results),
445
+ web_hits=len(uq.web_hits),
446
+ mcp_hits=len(uq.mcp.hits),
447
+ sources_queried=uq.mcp.sources_queried,
448
+ tokens_used=uq.debug.total_tokens_used,
449
+ generation_time_sec=elapsed,
450
+ )
451
+
452
+
453
+ def _effective_topic(topic: str, source_tc: Optional[str]) -> str:
454
+ t = (topic or "").strip()
455
+ if t:
456
+ return t
457
+ blob = (source_tc or "").strip()
458
+ if not blob:
459
+ return "Manual test case"
460
+ first = blob.split("\n", 1)[0].strip()
461
+ return (first[:120] if first else "Manual test case")
462
+
463
+
464
+ @app.post("/generate", response_model=GenerateResponse, dependencies=[Depends(require_scope("write", "admin"))])
465
+ async def generate_full_test(req: GenerateRequest):
466
+ """
467
+ Generate a complete test case (Markdown spec + Python pytest file).
468
+ Uses the same pipeline as the CLI: classifies input (synthesize vs transform vs expand),
469
+ then returns content as strings (artifacts are written to a temporary directory server-side).
470
+ """
471
+ effective_topic = _effective_topic(req.topic, req.source_tc)
472
+ mode = (req.generation_mode or cfg.DEFAULT_GENERATION_MODE or "pipeline").lower()
473
+
474
+ if mode == "agentic" and cfg.AGENTIC_ENABLED:
475
+ from knowledge_base.agents.graph import generate_agentic
476
+ result = generate_agentic(
477
+ effective_topic,
478
+ product=req.product,
479
+ top_k=req.top_k,
480
+ output_format=req.output_format,
481
+ test_id=req.test_id,
482
+ )
483
+ warnings = list(result.get("validation_warnings") or [])
484
+ confidence = result.get("confidence")
485
+ _apply_confidence_gate(confidence, warnings)
486
+ return GenerateResponse(
487
+ test_id=result.get("test_id") or req.test_id or "TC_AGENT",
488
+ topic=effective_topic,
489
+ feature_area=result.get("feature_area", "Unknown"),
490
+ markdown_content=result.get("markdown_output"),
491
+ python_content=result.get("python_output"),
492
+ kb_sources=result.get("sources_used") or [],
493
+ kb_chunk_count=len(result.get("rag_chunks") or []),
494
+ tokens_used=0,
495
+ generation_time_sec=0.0,
496
+ syntax_valid=not bool(result.get("validation_errors")),
497
+ warnings=warnings,
498
+ traceability_table="",
499
+ confidence=confidence,
500
+ precedence_conflicts=[],
501
+ )
502
+
503
+ try:
504
+ with tempfile.TemporaryDirectory() as td:
505
+ out = await _run_blocking(
506
+ lambda: generate_test_cases(
507
+ topic=effective_topic,
508
+ output_format=req.output_format,
509
+ product=req.product,
510
+ test_id=req.test_id or _next_test_id(),
511
+ feature_area=req.feature_area,
512
+ top_k=req.top_k,
513
+ output_dir=Path(td),
514
+ debug_retrieval=False,
515
+ source_tc=req.source_tc,
516
+ detail_level=req.detail_level,
517
+ )
518
+ )
519
+ response = _build_generate_response(out, effective_topic)
520
+ except ValueError as e:
521
+ raise HTTPException(404, detail=str(e))
522
+
523
+ return response
524
+
525
+
526
+ @app.post("/convert", response_model=GenerateResponse, dependencies=[Depends(require_scope("write", "admin"))])
527
+ async def convert_manual_test_case(req: ConvertRequest):
528
+ """
529
+ Convert pasted manual TC (gold or partial) to Markdown + pytest with fidelity constraints.
530
+ `source_tc` is required; retrieval is light (supplementary) for transform, full for expand.
531
+ """
532
+ if not req.source_tc.strip():
533
+ raise HTTPException(400, detail="source_tc cannot be empty")
534
+
535
+ effective_topic = _effective_topic(req.topic, req.source_tc)
536
+
537
+ try:
538
+ with tempfile.TemporaryDirectory() as td:
539
+ out = await _run_blocking(
540
+ lambda: generate_test_cases(
541
+ topic=effective_topic,
542
+ output_format=req.output_format,
543
+ product=req.product,
544
+ test_id=req.test_id or _next_test_id(),
545
+ feature_area=req.feature_area,
546
+ top_k=req.top_k,
547
+ output_dir=Path(td),
548
+ debug_retrieval=False,
549
+ source_tc=req.source_tc,
550
+ detail_level=req.detail_level,
551
+ )
552
+ )
553
+ response = _build_generate_response(out, effective_topic)
554
+ except ValueError as e:
555
+ raise HTTPException(404, detail=str(e))
556
+
557
+ return response
558
+
559
+
560
+ @app.post("/feedback", response_model=FeedbackResponse, dependencies=[Depends(require_scope("admin"))])
561
+ async def submit_feedback(req: FeedbackRequest):
562
+ """Capture user feedback for later curation (Phase 1 feedback loop)."""
563
+ try:
564
+ feedback_id = capture_feedback(
565
+ query=req.query,
566
+ generated_output=req.generated_output,
567
+ feedback_type=req.feedback_type,
568
+ feedback_data=req.feedback_data,
569
+ generation_metadata=req.generation_metadata,
570
+ user_id=req.user_id,
571
+ session_id=req.session_id,
572
+ source_context=req.source_context,
573
+ )
574
+ except ValueError as e:
575
+ raise HTTPException(400, detail=str(e))
576
+ except Exception as e:
577
+ logger.exception("Failed to capture feedback")
578
+ raise HTTPException(500, detail=f"Failed to store feedback: {e}")
579
+
580
+ return FeedbackResponse(feedback_id=feedback_id)
581
+
582
+
583
+ @app.post("/generate/v2", response_model=AgenticGenerateResponse, dependencies=[Depends(require_scope("write", "admin"))])
584
+ async def generate_agentic(req: AgenticGenerateRequest):
585
+ """Agentic generation using LangGraph multi-agent pipeline (Phase 5)."""
586
+ if not cfg.AGENTIC_ENABLED:
587
+ raise HTTPException(503, detail="Agentic pipeline disabled (AGENTIC_ENABLED=false)")
588
+
589
+ from knowledge_base.agents.graph import generate_agentic
590
+
591
+ try:
592
+ result = generate_agentic(
593
+ req.query,
594
+ product=req.product,
595
+ top_k=req.top_k,
596
+ output_format=req.output_format,
597
+ test_id=req.test_id,
598
+ )
599
+ except Exception as e:
600
+ logger.exception("Agentic generation failed")
601
+ raise HTTPException(500, detail=str(e))
602
+
603
+ warnings: List[str] = list(result.get("validation_warnings") or [])
604
+ confidence = result.get("confidence")
605
+ _apply_confidence_gate(confidence, warnings)
606
+
607
+ return AgenticGenerateResponse(
608
+ test_id=result.get("test_id") or req.test_id or "TC_AGENT",
609
+ query=req.query,
610
+ feature_area=result.get("feature_area", "Unknown"),
611
+ markdown_content=result.get("markdown_output"),
612
+ python_content=result.get("python_output"),
613
+ confidence=confidence,
614
+ sources_used=result.get("sources_used") or [],
615
+ agent_trace=result.get("agent_trace") or [],
616
+ validation_errors=result.get("validation_errors") or [],
617
+ validation_warnings=result.get("validation_warnings") or [],
618
+ warnings=warnings,
619
+ )
620
+
621
+
622
+ @app.post("/rca", dependencies=[Depends(require_scope("admin"))])
623
+ async def run_rca(req: RCARequest):
624
+ """Root cause analysis for test failures (Phase 7)."""
625
+ from knowledge_base.agents.rca_agent import analyze_failure
626
+ return analyze_failure(req.test_id, req.failure_log)
627
+
628
+
629
+ @app.post("/heal", dependencies=[Depends(require_scope("admin"))])
630
+ async def heal_test(req: HealRequest):
631
+ """Propose script fix for flaky test (Phase 7)."""
632
+ from knowledge_base.agents.healing_agent import propose_fix
633
+ return propose_fix(req.test_id, req.flaky_history)
634
+
635
+
636
+ @app.post("/impact-analysis", dependencies=[Depends(require_scope("admin"))])
637
+ async def analyze_impact(req: ImpactAnalysisRequest):
638
+ """Regression impact analysis from changed files (Phase 7)."""
639
+ from knowledge_base.agents.regression_agent import impact_analysis
640
+ return impact_analysis(req.changed_files)
641
+
642
+
643
+ @app.get("/feedback/report", dependencies=[Depends(require_scope("admin"))])
644
+ async def feedback_report(days: int = 7):
645
+ """Weekly feedback analytics report (Phase 2)."""
646
+ from knowledge_base.feedback.analytics import generate_weekly_report
647
+ return generate_weekly_report(days=days)
648
+
649
+
650
+ # ── Helpers ───────────────────────────────────────────────────────────────────
651
+
652
+ _QUERY_ADDENDUM = """
653
+
654
+ ADDITIONAL INSTRUCTION FOR QUERY MODE
655
+ ======================================
656
+ The user is asking a question (not necessarily requesting a full JIRA test case).
657
+ Respond with the most detailed and accurate answer the KB context supports:
658
+ - If they ask for test steps, give FULL executable steps (navigation paths, exact CLI, expected results).
659
+ - If they ask about a feature, explain it with exact registry keys and settings from context.
660
+ - If they ask how to configure something, provide the exact TC Setup path and values.
661
+ - Always cite which SOURCE/KB_REF the information comes from.
662
+ - Never invent steps, paths, or URLs not present in the context.
663
+ - If the KB does not contain enough info, say so explicitly — do not guess.
664
+ """
665
+
666
+
667
+ def _read_output_files(out) -> Tuple[Optional[str], Optional[str]]:
668
+ md_content: Optional[str] = None
669
+ py_content: Optional[str] = None
670
+ if out.markdown_path and out.markdown_path.exists():
671
+ md_content = out.markdown_path.read_text(encoding="utf-8")
672
+ if out.python_path and out.python_path.exists():
673
+ py_content = out.python_path.read_text(encoding="utf-8")
674
+ return md_content, py_content
675
+
676
+
677
+ def _apply_confidence_gate(confidence: Optional[dict], warnings: List[str]) -> None:
678
+ """Reject or warn based on publish confidence score (Phase 1)."""
679
+ if not confidence:
680
+ return
681
+
682
+ publish_score = float(confidence.get("publish", 0.0))
683
+ route = confidence.get("route")
684
+
685
+ if route == "reject" or publish_score < cfg.CONFIDENCE_REJECT_THRESHOLD:
686
+ raise HTTPException(
687
+ status_code=400,
688
+ detail={
689
+ "message": "Generation confidence too low to publish output.",
690
+ "publish_score": publish_score,
691
+ "threshold": cfg.CONFIDENCE_REJECT_THRESHOLD,
692
+ "confidence": confidence,
693
+ "recommendation": confidence.get("publish_recommendation", ""),
694
+ },
695
+ )
696
+
697
+ if route == "human_review" or publish_score < cfg.CONFIDENCE_WARN_THRESHOLD:
698
+ warnings.append(
699
+ f"⚠️ LOW CONFIDENCE: publish score {publish_score:.2f} — human review recommended."
700
+ )
701
+
702
+
703
+ def _build_generate_response(out, effective_topic: str) -> GenerateResponse:
704
+ md_content, py_content = _read_output_files(out)
705
+ warnings = list(out.warnings)
706
+ _apply_confidence_gate(out.confidence, warnings)
707
+
708
+ if out.precedence_conflicts:
709
+ warnings.append(
710
+ f"⚠️ SOURCE CONFLICTS: {len(out.precedence_conflicts)} precedence conflict(s) detected — reviewer should verify."
711
+ )
712
+
713
+ return GenerateResponse(
714
+ test_id=out.test_id,
715
+ topic=effective_topic,
716
+ feature_area=out.feature_area,
717
+ markdown_content=md_content,
718
+ python_content=py_content,
719
+ kb_sources=out.kb_sources,
720
+ kb_chunk_count=out.kb_chunk_count,
721
+ tokens_used=out.tokens_used,
722
+ generation_time_sec=out.generation_time_sec,
723
+ syntax_valid=out.syntax_valid,
724
+ warnings=warnings,
725
+ traceability_table=out.traceability_table or "",
726
+ confidence=out.confidence,
727
+ precedence_conflicts=out.precedence_conflicts,
728
+ )
729
+
730
+
731
+ def _next_test_id() -> str:
732
+ """Generate next TC id from the database (not file-system based)."""
733
+ try:
734
+ with get_conn() as conn:
735
+ with conn.cursor() as cur:
736
+ cur.execute("""
737
+ SELECT metadata->>'test_id' FROM kb_documents
738
+ WHERE metadata->>'test_id' IS NOT NULL
739
+ ORDER BY ingested_at DESC LIMIT 1
740
+ """)
741
+ row = cur.fetchone()
742
+ except Exception:
743
+ row = None
744
+ import re
745
+ suffix = uuid4().hex[:4].upper()
746
+ if row and row[0]:
747
+ m = re.search(r"\d+", row[0])
748
+ if m:
749
+ return f"TC{int(m.group()) + 1:03d}_{suffix}"
750
+ return f"TC{int(time.time() * 1000) % 1000000:06d}_{suffix}"
751
+
752
+
753
+ def _component_health(component: str) -> Optional[dict]:
754
+ try:
755
+ if component == "neo4j":
756
+ from knowledge_base.graph_db.neo4j_client import get_neo4j_client
757
+ return get_neo4j_client().health_check()
758
+ if component == "cognee":
759
+ from knowledge_base.memory.cognee_client import get_cognee_client
760
+ return get_cognee_client().health_check()
761
+ if component == "graphiti":
762
+ from knowledge_base.graph_db.graphiti_client import get_graphiti_client
763
+ return get_graphiti_client().health_check()
764
+ except Exception as e:
765
+ return {"status": "error", "error": str(e)}
766
+ return None
767
+
768
+
769
+ def _current_phase() -> str:
770
+ if cfg.AGENTIC_ENABLED:
771
+ return "phase5+"
772
+ if cfg.GRAPHITI_ENABLED:
773
+ return "phase6"
774
+ if cfg.COGNEE_ENABLED:
775
+ return "phase4"
776
+ if cfg.KG_SHADOW_MODE or cfg.KG_BACKEND == "neo4j":
777
+ return "phase3"
778
+ return "phase1"