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,93 @@
1
+ """Apply curated feedback to the knowledge base — Phase 2."""
2
+ from __future__ import annotations
3
+
4
+ import logging
5
+ import re
6
+ from typing import Any
7
+
8
+ from knowledge_base.db.client import get_conn
9
+ from knowledge_base.db.feedback_schema import get_feedback_event, list_promotions
10
+ from knowledge_base.db.schema import ensure_knowledge_graph_tables
11
+ from knowledge_base.feedback.handler import apply_feedback, apply_promotion
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ _REGISTRY_RE = re.compile(r"\b([a-z][a-z0-9_]*(?:\.[a-z0-9_]+)+)\b", re.I)
16
+ _UI_PATH_RE = re.compile(r"(UMS[^>\n]{3,200})", re.I)
17
+
18
+
19
+ def apply_curated_feedback(event_id: str, actor_id: str | None = None) -> dict[str, Any]:
20
+ """
21
+ Apply an approved feedback event to the system.
22
+
23
+ Strategies:
24
+ - registry_key corrections → upsert into KG
25
+ - ui_path corrections → upsert into KG
26
+ - pattern corrections → stored for Cognee ingestion (Phase 4)
27
+ """
28
+ event = get_feedback_event(event_id)
29
+ if not event:
30
+ raise ValueError(f"Event not found: {event_id}")
31
+ if event["status"] != "approved":
32
+ raise ValueError(f"Event must be approved before apply (status={event['status']})")
33
+
34
+ ensure_knowledge_graph_tables()
35
+ applied: list[str] = []
36
+ feedback_data = event.get("feedback_data") or {}
37
+
38
+ registry_key = feedback_data.get("registry_key") or feedback_data.get("corrected_registry_key")
39
+ ui_path = feedback_data.get("ui_path") or feedback_data.get("corrected_ui_path")
40
+
41
+ if registry_key:
42
+ _upsert_kg_node("registry_key", registry_key)
43
+ applied.append(f"registry_key:{registry_key}")
44
+
45
+ if ui_path:
46
+ _upsert_kg_node("ui_path", ui_path)
47
+ applied.append(f"ui_path:{ui_path}")
48
+
49
+ correction_text = feedback_data.get("correction") or feedback_data.get("notes") or ""
50
+ if correction_text and not registry_key:
51
+ for key in _REGISTRY_RE.findall(correction_text):
52
+ if key.count(".") >= 2:
53
+ _upsert_kg_node("registry_key", key)
54
+ applied.append(f"registry_key:{key}")
55
+
56
+ if correction_text and not ui_path:
57
+ for path in _UI_PATH_RE.findall(correction_text):
58
+ if ">" in path or "Console" in path:
59
+ _upsert_kg_node("ui_path", path.strip())
60
+ applied.append(f"ui_path:{path.strip()}")
61
+
62
+ action = "updated_kg" if applied else "refined_prompt"
63
+ apply_feedback(event_id, action, actor_id=actor_id)
64
+
65
+ return {"event_id": event_id, "applied_action": action, "changes": applied}
66
+
67
+
68
+ def apply_pending_promotions(limit: int = 10, actor_id: str | None = None) -> list[dict]:
69
+ """Apply all pending promotions (batch)."""
70
+ promotions = list_promotions(status="pending", limit=limit)
71
+ results = []
72
+ for promo in promotions:
73
+ try:
74
+ result = apply_curated_feedback(promo["feedback_event_id"], actor_id=actor_id)
75
+ apply_promotion(promo["id"], actor_id=actor_id)
76
+ result["promotion_id"] = promo["id"]
77
+ results.append(result)
78
+ except Exception as e:
79
+ logger.error("Failed to apply promotion %s: %s", promo["id"], e)
80
+ results.append({"promotion_id": promo["id"], "error": str(e)})
81
+ return results
82
+
83
+
84
+ def _upsert_kg_node(node_type: str, value: str) -> None:
85
+ normalized = value.lower()[:2000]
86
+ sql = """
87
+ INSERT INTO knowledge_graph_nodes (node_type, value, normalized)
88
+ VALUES (%s, %s, %s)
89
+ ON CONFLICT (node_type, normalized) DO UPDATE SET value = EXCLUDED.value
90
+ """
91
+ with get_conn() as conn:
92
+ with conn.cursor() as cur:
93
+ cur.execute(sql, (node_type, value[:4000], normalized))
@@ -0,0 +1,49 @@
1
+ """Feedback curation logic — Phase 2 human-in-the-loop workflow."""
2
+ from __future__ import annotations
3
+
4
+ import logging
5
+
6
+ from knowledge_base.db.feedback_schema import get_feedback_event, list_feedback_events, list_promotions
7
+ from knowledge_base.feedback.handler import apply_feedback, apply_promotion, curate_feedback
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ def list_pending(limit: int = 10, offset: int = 0) -> list[dict]:
13
+ return list_feedback_events(status="pending", limit=limit, offset=offset)
14
+
15
+
16
+ def get_event(event_id: str) -> dict | None:
17
+ return get_feedback_event(event_id)
18
+
19
+
20
+ def approve(event_id: str, curator_id: str, notes: str | None = None) -> None:
21
+ """Approve feedback and create promotion record."""
22
+ event = get_feedback_event(event_id)
23
+ if not event:
24
+ raise ValueError(f"Feedback event not found: {event_id}")
25
+ if event["status"] not in ("pending", "reviewed"):
26
+ raise ValueError(f"Cannot approve event in status '{event['status']}'")
27
+
28
+ curate_feedback(event_id, "approved", curator_id, notes)
29
+ logger.info("Feedback approved: %s by %s", event_id, curator_id)
30
+
31
+
32
+ def reject(event_id: str, curator_id: str, notes: str | None = None) -> None:
33
+ event = get_feedback_event(event_id)
34
+ if not event:
35
+ raise ValueError(f"Feedback event not found: {event_id}")
36
+ curate_feedback(event_id, "rejected", curator_id, notes)
37
+ logger.info("Feedback rejected: %s by %s", event_id, curator_id)
38
+
39
+
40
+ def mark_reviewed(event_id: str, curator_id: str, notes: str | None = None) -> None:
41
+ curate_feedback(event_id, "reviewed", curator_id, notes)
42
+
43
+
44
+ def list_approved_promotions(limit: int = 20) -> list[dict]:
45
+ return list_promotions(status="pending", limit=limit)
46
+
47
+
48
+ def list_applied_promotions(limit: int = 20) -> list[dict]:
49
+ return list_promotions(status="applied", limit=limit)
@@ -0,0 +1,151 @@
1
+ """
2
+ Feedback capture and processing for IGEL KB System (Phase 1+).
3
+
4
+ Two-step flow: Capture → Curate → Promote → Apply
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import logging
9
+ from typing import Any
10
+
11
+ from knowledge_base.db.feedback_schema import (
12
+ insert_feedback_audit,
13
+ insert_feedback_event,
14
+ insert_feedback_promotion,
15
+ mark_feedback_applied,
16
+ mark_promotion_applied,
17
+ update_feedback_curation,
18
+ )
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ VALID_FEEDBACK_TYPES = frozenset({
23
+ "rating",
24
+ "correction",
25
+ "edit",
26
+ "rejection",
27
+ "execution_failure",
28
+ })
29
+
30
+
31
+ def capture_feedback(
32
+ query: str,
33
+ generated_output: dict,
34
+ feedback_type: str,
35
+ feedback_data: dict,
36
+ *,
37
+ generation_metadata: dict | None = None,
38
+ user_id: str | None = None,
39
+ session_id: str | None = None,
40
+ source_context: dict | None = None,
41
+ ) -> str:
42
+ """
43
+ Capture a user feedback event. Returns feedback event UUID.
44
+ """
45
+ if feedback_type not in VALID_FEEDBACK_TYPES:
46
+ raise ValueError(
47
+ f"Invalid feedback_type '{feedback_type}'. "
48
+ f"Must be one of: {', '.join(sorted(VALID_FEEDBACK_TYPES))}"
49
+ )
50
+
51
+ event_id = insert_feedback_event(
52
+ query=query,
53
+ generated_output=generated_output,
54
+ feedback_type=feedback_type,
55
+ feedback_data=feedback_data,
56
+ generation_metadata=generation_metadata,
57
+ user_id=user_id,
58
+ session_id=session_id,
59
+ source_context=source_context,
60
+ )
61
+
62
+ insert_feedback_audit(
63
+ feedback_event_id=event_id,
64
+ action="feedback_captured",
65
+ actor_id=user_id,
66
+ details={"feedback_type": feedback_type},
67
+ )
68
+
69
+ logger.info("Feedback captured: id=%s type=%s", event_id, feedback_type)
70
+ return event_id
71
+
72
+
73
+ def curate_feedback(
74
+ event_id: str,
75
+ status: str,
76
+ curator_id: str,
77
+ curator_notes: str | None = None,
78
+ ) -> None:
79
+ """
80
+ Update curation status: reviewed, approved, rejected.
81
+ """
82
+ if status not in ("reviewed", "approved", "rejected"):
83
+ raise ValueError(f"Invalid curation status: {status}")
84
+
85
+ update_feedback_curation(
86
+ event_id=event_id,
87
+ status=status,
88
+ curator_id=curator_id,
89
+ curator_notes=curator_notes,
90
+ )
91
+
92
+ insert_feedback_audit(
93
+ feedback_event_id=event_id,
94
+ action=f"feedback_{status}",
95
+ actor_id=curator_id,
96
+ details={"curator_notes": curator_notes},
97
+ )
98
+
99
+ if status == "approved":
100
+ insert_feedback_promotion(
101
+ feedback_event_id=event_id,
102
+ curator_id=curator_id,
103
+ promoted_content={"curator_notes": curator_notes},
104
+ curator_notes=curator_notes,
105
+ )
106
+
107
+
108
+ def apply_feedback(
109
+ event_id: str,
110
+ applied_action: str,
111
+ actor_id: str | None = None,
112
+ ) -> None:
113
+ """
114
+ Mark feedback as applied to the system (KG update, prompt refinement, etc.).
115
+ """
116
+ mark_feedback_applied(event_id=event_id, applied_action=applied_action)
117
+ insert_feedback_audit(
118
+ feedback_event_id=event_id,
119
+ action="feedback_applied",
120
+ actor_id=actor_id,
121
+ details={"applied_action": applied_action},
122
+ )
123
+
124
+
125
+ def apply_promotion(promotion_id: str, actor_id: str | None = None) -> None:
126
+ """Mark a curated promotion as applied (ready for Cognee ingestion in Phase 4)."""
127
+ mark_promotion_applied(promotion_id=promotion_id, actor_id=actor_id)
128
+
129
+
130
+ def build_generation_output_payload(
131
+ *,
132
+ test_id: str,
133
+ topic: str,
134
+ feature_area: str,
135
+ markdown_content: str | None,
136
+ python_content: str | None,
137
+ kb_sources: list[str],
138
+ confidence: dict | None = None,
139
+ warnings: list[str] | None = None,
140
+ ) -> dict[str, Any]:
141
+ """Build a JSON-serializable payload for feedback storage."""
142
+ return {
143
+ "test_id": test_id,
144
+ "topic": topic,
145
+ "feature_area": feature_area,
146
+ "markdown": markdown_content,
147
+ "python": python_content,
148
+ "kb_sources": kb_sources,
149
+ "confidence": confidence,
150
+ "warnings": warnings or [],
151
+ }
@@ -0,0 +1,5 @@
1
+ """Test case generation pipeline."""
2
+
3
+ from . import test_generator
4
+
5
+ __all__ = ["test_generator"]
@@ -0,0 +1,148 @@
1
+ """
2
+ Classify user/test-case inputs for routing: transform (gold TC), expand (partial), or synthesize (topic-only).
3
+
4
+ Also picks target detail level (concise / detailed) for generation prompts.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import re
9
+ from dataclasses import dataclass
10
+ from enum import Enum
11
+
12
+ from knowledge_base.generation.manual_tc_parser import parse_manual_tc, count_parsed_steps
13
+
14
+
15
+ class InputMode(str, Enum):
16
+ """How the pipeline should treat the primary input."""
17
+
18
+ TRANSFORM = "transform" # Full gold TC — preserve structure
19
+ EXPAND = "expand" # Partial TC — fill gaps only
20
+ SYNTHESIZE = "synthesize" # Topic/query only — RAG synthesis
21
+
22
+
23
+ class DetailLevel(str, Enum):
24
+ CONCISE = "concise"
25
+ DETAILED = "detailed"
26
+ AUTO = "auto"
27
+
28
+
29
+ @dataclass
30
+ class Classification:
31
+ mode: InputMode
32
+ detail_level: DetailLevel
33
+ reason: str
34
+ parsed_step_count: int
35
+
36
+
37
+ _JIRA_KEY = re.compile(
38
+ r"\b(QCL|QCAPPS|QC|TC)-?\d+\b",
39
+ re.IGNORECASE,
40
+ )
41
+
42
+
43
+ def classify_input(
44
+ topic: str,
45
+ source_tc: str | None,
46
+ *,
47
+ detail_level: DetailLevel | str = DetailLevel.AUTO,
48
+ ) -> Classification:
49
+ """
50
+ Decide pipeline mode from topic + optional pasted manual test case text.
51
+
52
+ Rules:
53
+ - If `source_tc` is non-empty and parses as 3+ structured steps → TRANSFORM.
54
+ - If `source_tc` is non-empty but 1–2 steps or missing expected results → EXPAND.
55
+ - If only `topic` with structured steps inside topic (rare) → TRANSFORM if parse succeeds.
56
+ - Else → SYNTHESIZE.
57
+ """
58
+ raw = (source_tc or "").strip()
59
+ topic_clean = (topic or "").strip()
60
+
61
+ # Explicit detail override
62
+ if isinstance(detail_level, str):
63
+ try:
64
+ dl = DetailLevel(detail_level.lower())
65
+ except ValueError:
66
+ dl = DetailLevel.AUTO
67
+ else:
68
+ dl = detail_level
69
+
70
+ # Heuristic: partial = has JIRA id + narrative but few steps
71
+ if raw or (not raw and _looks_like_pasted_tc(topic_clean)):
72
+ combined = raw or topic_clean
73
+ has_jira = bool(_JIRA_KEY.search(combined))
74
+ has_expected_col = "expected result" in combined.lower()
75
+ steps = parse_manual_tc(combined)
76
+ n = count_parsed_steps(steps)
77
+
78
+ if n >= 3 and _steps_look_complete(steps, has_expected_col):
79
+ reason = f"Structured TC with {n} steps — transform mode (preserve fidelity)."
80
+ return Classification(
81
+ InputMode.TRANSFORM,
82
+ _resolve_detail(dl, n, topic_clean),
83
+ reason,
84
+ n,
85
+ )
86
+ if n >= 1 or (has_jira and len(combined) > 200):
87
+ reason = (
88
+ f"Partial TC ({n} parsed steps) — expand mode with KB fill-in."
89
+ if n
90
+ else "JIRA-style text with little step structure — expand mode."
91
+ )
92
+ return Classification(
93
+ InputMode.EXPAND,
94
+ _resolve_detail(dl, n, topic_clean or combined[:80]),
95
+ reason,
96
+ n,
97
+ )
98
+
99
+ # Topic-only synthesis
100
+ return Classification(
101
+ InputMode.SYNTHESIZE,
102
+ _resolve_detail(dl, 0, topic_clean),
103
+ "No structured manual TC provided — synthesize from KB.",
104
+ 0,
105
+ )
106
+
107
+
108
+ def _looks_like_pasted_tc(text: str) -> bool:
109
+ if not text or len(text) < 80:
110
+ return False
111
+ low = text.lower()
112
+ if "test data" in low and ("expected result" in low or "step" in low):
113
+ return True
114
+ if "#,step,test data" in low.replace(" ", "").lower():
115
+ return True
116
+ if re.search(r"(?m)^\s*\"?#\"?\s*,\s*Step\s*,\s*Test Data", text, re.I):
117
+ return True
118
+ # Many numbered lines like "1,Verify"
119
+ numbered = len(re.findall(r"(?m)^\s*\d+\s*,\s*[^\n]", text))
120
+ return numbered >= 3
121
+
122
+
123
+ def _steps_look_complete(steps: list, has_expected_column_hint: bool) -> bool:
124
+ if not steps:
125
+ return False
126
+ # JIRA/Confluence exports often list "Expected Result" in the header but cells are not parsed.
127
+ if has_expected_column_hint and len(steps) >= 3:
128
+ return True
129
+ with_exp = sum(1 for s in steps if (s.expected_result or "").strip())
130
+ if has_expected_column_hint and with_exp < max(1, len(steps) // 3):
131
+ return False
132
+ titled = sum(1 for s in steps if (s.step_title or "").strip())
133
+ return titled >= max(1, len(steps) // 2)
134
+
135
+
136
+ def _resolve_detail(dl: DetailLevel, step_count: int, topic: str) -> DetailLevel:
137
+ if dl != DetailLevel.AUTO:
138
+ return dl
139
+ low = topic.lower()
140
+ if "brief" in low or "concise" in low or "short" in low:
141
+ return DetailLevel.CONCISE
142
+ if "detailed" in low or "novice" in low or "verbose" in low:
143
+ return DetailLevel.DETAILED
144
+ if step_count >= 12:
145
+ return DetailLevel.DETAILED
146
+ if step_count >= 4:
147
+ return DetailLevel.CONCISE
148
+ return DetailLevel.DETAILED
@@ -0,0 +1,180 @@
1
+ """
2
+ Parse pasted IGEL manual test cases (CSV export or JIRA-ish markdown) into structured steps.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import csv
7
+ import io
8
+ import re
9
+ from dataclasses import dataclass
10
+
11
+
12
+ @dataclass
13
+ class ParsedStep:
14
+ step_id: str
15
+ step_title: str
16
+ test_data: str
17
+ expected_result: str
18
+
19
+
20
+ def parse_manual_tc(text: str) -> list[ParsedStep]:
21
+ text = (text or "").strip()
22
+ if not text:
23
+ return []
24
+
25
+ low_head = text[:800].lower()
26
+ if "test data" in low_head and "expected result" in low_head and _is_csv_like(text):
27
+ parsed = _parse_csv_tc(text)
28
+ if parsed:
29
+ return parsed
30
+
31
+ if re.search(r"(?m)^\s*\"?#\"?\s*,\s*step\s*,", text[:2000], re.I):
32
+ parsed = _parse_csv_tc(text)
33
+ if parsed:
34
+ return parsed
35
+
36
+ jira_steps = _parse_jira_id_column_layout(text)
37
+ if len(jira_steps) >= 3:
38
+ return jira_steps
39
+
40
+ return _parse_loose_numbered_tc(text)
41
+
42
+
43
+ def count_parsed_steps(steps: list[ParsedStep]) -> int:
44
+ return len(steps)
45
+
46
+
47
+ def _is_csv_like(text: str) -> bool:
48
+ first = text.split("\n", 1)[0]
49
+ return first.count(",") >= 2
50
+
51
+
52
+ def _parse_csv_tc(text: str) -> list[ParsedStep]:
53
+ """Handle TestSteps*.csv style: #,Step,Test Data,Expected Result"""
54
+ stream = io.StringIO(text)
55
+ try:
56
+ reader = csv.DictReader(stream)
57
+ except Exception:
58
+ return []
59
+ if not reader.fieldnames:
60
+ return []
61
+
62
+ lower_to_key: dict[str, str] = {}
63
+ for k in reader.fieldnames:
64
+ lower_to_key[k.strip().strip('"').lower()] = k
65
+
66
+ def pick(*cands: str) -> str | None:
67
+ for c in cands:
68
+ lk = c.lower().strip()
69
+ if lk in lower_to_key:
70
+ return lower_to_key[lk]
71
+ return None
72
+
73
+ k_id = pick("#", "step id", "id")
74
+ k_step = pick("step")
75
+ k_data = pick("test data", "testdata")
76
+ k_exp = pick("expected result", "expected")
77
+
78
+ steps: list[ParsedStep] = []
79
+ for row in reader:
80
+ if not row:
81
+ continue
82
+ if not any((row.get(k) or "").strip() for k in reader.fieldnames):
83
+ continue
84
+
85
+ def gv(k: str | None) -> str:
86
+ if not k:
87
+ return ""
88
+ return (row.get(k) or "").strip()
89
+
90
+ sid = gv(k_id)
91
+ title = gv(k_step)
92
+ data = gv(k_data)
93
+ exp = gv(k_exp)
94
+ if not data and title:
95
+ data = title
96
+ if not sid and not title and not data:
97
+ continue
98
+ steps.append(
99
+ ParsedStep(
100
+ step_id=sid or str(len(steps) + 1),
101
+ step_title=title or (data[:120] if data else f"Step {len(steps) + 1}"),
102
+ test_data=data or title,
103
+ expected_result=exp,
104
+ )
105
+ )
106
+ return steps
107
+
108
+
109
+ def _parse_jira_id_column_layout(text: str) -> list[ParsedStep]:
110
+ """
111
+ JIRA / Confluence exports: step id alone on a line, following lines are step content until next id.
112
+ """
113
+ lines = text.splitlines()
114
+ steps: list[ParsedStep] = []
115
+ i = 0
116
+ while i < len(lines):
117
+ raw = lines[i]
118
+ stripped = raw.strip()
119
+ if re.match(r"^\d{1,3}$", stripped):
120
+ sid = stripped
121
+ i += 1
122
+ buf: list[str] = []
123
+ while i < len(lines):
124
+ nxt = lines[i].strip()
125
+ if re.match(r"^\d{1,3}$", nxt) and not buf:
126
+ break
127
+ if re.match(r"^\d{1,3}$", nxt) and buf:
128
+ break
129
+ buf.append(lines[i].rstrip())
130
+ i += 1
131
+ blob = "\n".join(buf).strip()
132
+ if blob and len(blob) > 3:
133
+ title = blob.split("\n", 1)[0].strip()[:200]
134
+ steps.append(
135
+ ParsedStep(
136
+ step_id=sid,
137
+ step_title=title,
138
+ test_data=blob,
139
+ expected_result="",
140
+ )
141
+ )
142
+ continue
143
+ i += 1
144
+ return steps
145
+
146
+
147
+ def _parse_loose_numbered_tc(text: str) -> list[ParsedStep]:
148
+ """
149
+ Best-effort for JIRA PDF/markdown exports: find lines starting with an integer step id.
150
+ """
151
+ steps: list[ParsedStep] = []
152
+ # Pattern: start of line, digits, whitespace, rest of line
153
+ parts = re.split(r"(?m)^(\d{1,3})\s{2,}(.+?)\s*$", text)
154
+ # re.split with groups returns alternating chunks; skip preamble
155
+ i = 1
156
+ while i < len(parts):
157
+ if i + 1 >= len(parts):
158
+ break
159
+ sid = parts[i]
160
+ first_line = parts[i + 1].strip()
161
+ i += 2
162
+ block = first_line
163
+ # Attach following non-numbered lines until next step
164
+ # This is heuristic — export formats vary
165
+ steps.append(
166
+ ParsedStep(
167
+ step_id=sid,
168
+ step_title=first_line[:120],
169
+ test_data=block,
170
+ expected_result="",
171
+ )
172
+ )
173
+ if len(steps) >= 2:
174
+ return steps
175
+
176
+ # Fallback: lines like "1,Step title,..." CSV without header
177
+ for m in re.finditer(r"(?m)^\s*(\d+)\s*,\s*([^,\n]+)(?:\s*,\s*([^\n]*))?", text):
178
+ sid, t1, t2 = m.group(1), m.group(2).strip(), (m.group(3) or "").strip()
179
+ steps.append(ParsedStep(step_id=sid, step_title=t1, test_data=t2 or t1, expected_result=""))
180
+ return steps