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
File without changes
@@ -0,0 +1,402 @@
1
+ """
2
+ Production-ready Hierarchical Chunker for IGEL Knowledge Base.
3
+
4
+ Strategy:
5
+ 1. Each document section → one or more Parent Chunks (800–1200 tokens)
6
+ 2. Each Parent Chunk → 2–5 Child Chunks (150–300 tokens) for vector indexing
7
+ 3. Step sequences are NEVER split across chunk boundaries
8
+ 4. Tables are kept whole (header always included)
9
+ 5. 80-token overlap between sibling children to prevent context boundary loss
10
+ 6. Each chunk validated before being returned
11
+ """
12
+ from __future__ import annotations
13
+ import re
14
+ from dataclasses import dataclass, field
15
+ from typing import Generator
16
+
17
+ import tiktoken
18
+
19
+ from knowledge_base.parsers.markdown_parser import Section
20
+ from knowledge_base.chunking.validators import (
21
+ ValidationResult,
22
+ validate_chunk,
23
+ validate_coverage,
24
+ classify_chunk_type,
25
+ contains_steps,
26
+ detect_product,
27
+ CHUNK_TOKEN_LIMITS,
28
+ )
29
+ from knowledge_base.config import cfg
30
+
31
+ _ENCODER = tiktoken.get_encoding("cl100k_base")
32
+
33
+ PARENT_MAX_TOKENS = getattr(cfg, "CHUNK_PARENT_MAX_TOKENS", 1024)
34
+ PARENT_MIN_TOKENS = getattr(cfg, "CHUNK_PARENT_MIN_TOKENS", 80)
35
+ CHILD_MAX_TOKENS = getattr(cfg, "CHUNK_CHILD_MAX_TOKENS", 256)
36
+ CHILD_MIN_TOKENS = getattr(cfg, "CHUNK_CHILD_MIN_TOKENS", 50)
37
+ OVERLAP_TOKENS = getattr(cfg, "CHUNK_OVERLAP_TOKENS", 64)
38
+ CONTEXT_HEADERS = getattr(cfg, "CHUNK_CONTEXT_HEADERS", True)
39
+
40
+
41
+ @dataclass
42
+ class ParentChunk:
43
+ content: str
44
+ section_title: str
45
+ chunk_type: str
46
+ page_number: int
47
+ token_count: int
48
+ metadata: dict = field(default_factory=dict)
49
+ children: list["ChildChunk"] = field(default_factory=list)
50
+
51
+
52
+ @dataclass
53
+ class ChildChunk:
54
+ content: str
55
+ chunk_type: str
56
+ token_count: int
57
+ contains_steps: bool
58
+ has_table: bool
59
+ metadata: dict = field(default_factory=dict)
60
+ validation: ValidationResult = field(default_factory=lambda: ValidationResult(passed=True))
61
+
62
+
63
+ def count_tokens(text: str) -> int:
64
+ return len(_ENCODER.encode(text))
65
+
66
+
67
+ def decode_tokens(tokens: list[int]) -> str:
68
+ return _ENCODER.decode(tokens)
69
+
70
+
71
+ def chunk_sections(
72
+ sections: list[Section],
73
+ file_name: str,
74
+ product: str | None = None,
75
+ ) -> tuple[list[ParentChunk], list[str]]:
76
+ """
77
+ Entry point: convert document sections into parent+child chunk hierarchy.
78
+ Returns (parent_chunks, validation_warnings).
79
+ """
80
+ all_parents: list[ParentChunk] = []
81
+ all_warnings: list[str] = []
82
+
83
+ for section in sections:
84
+ if not section.content.strip():
85
+ continue
86
+
87
+ chunk_type = classify_chunk_type(section.content, section.title)
88
+ detected_product = product or detect_product(section.content, file_name)
89
+
90
+ base_metadata = {
91
+ "source": file_name,
92
+ "product": detected_product,
93
+ "section": section.title,
94
+ }
95
+
96
+ parents = _build_parent_chunks(section, chunk_type, base_metadata)
97
+
98
+ for parent in parents:
99
+ parent.children = _build_child_chunks(parent, base_metadata)
100
+ all_parents.append(parent)
101
+
102
+ # Collect validation warnings from children
103
+ for child in parent.children:
104
+ if not child.validation.passed:
105
+ all_warnings.append(
106
+ f"[{file_name}] Section '{section.title[:40]}': {child.validation.errors}"
107
+ )
108
+ for w in child.validation.warnings:
109
+ all_warnings.append(f"[{file_name}] Section '{section.title[:40]}': WARNING {w}")
110
+
111
+ # Coverage check across all parent chunks
112
+ original_tokens = sum(count_tokens(s.content) for s in sections if s.content.strip())
113
+ parent_tokens = [count_tokens(p.content) for p in all_parents]
114
+ try:
115
+ validate_coverage(original_tokens, parent_tokens)
116
+ except ValueError as e:
117
+ all_warnings.append(f"[{file_name}] COVERAGE WARNING: {e}")
118
+
119
+ return all_parents, all_warnings
120
+
121
+
122
+ # ── Parent Chunk Building ─────────────────────────────────────────────────────
123
+
124
+ def _build_parent_chunks(section: Section, chunk_type: str, base_metadata: dict) -> list[ParentChunk]:
125
+ total_tokens = count_tokens(section.content)
126
+
127
+ # Small section → single parent
128
+ if total_tokens <= PARENT_MAX_TOKENS:
129
+ return [_make_parent(section.content, section.title, chunk_type, section.page_hint, base_metadata)]
130
+
131
+ # Large section → split carefully
132
+ return _split_large_section(section, chunk_type, base_metadata)
133
+
134
+
135
+ def _split_large_section(section: Section, chunk_type: str, base_metadata: dict) -> list[ParentChunk]:
136
+ parents: list[ParentChunk] = []
137
+ text = section.content
138
+
139
+ # Tables → keep whole regardless of size
140
+ if chunk_type == "table" or ("|" in text and text.count("|") > 6):
141
+ return [_make_parent(text, section.title, "table", section.page_hint, base_metadata)]
142
+
143
+ # Find step blocks — extract them as atomic units
144
+ step_block, before, after = _extract_step_block(text)
145
+
146
+ if step_block:
147
+ # Before-steps content
148
+ if before.strip():
149
+ for chunk in _split_by_paragraph(before, PARENT_MAX_TOKENS):
150
+ parents.append(_make_parent(chunk, section.title, chunk_type, section.page_hint, base_metadata))
151
+
152
+ # Step block — if too large, split only between step groups
153
+ step_tokens = count_tokens(step_block)
154
+ if step_tokens <= PARENT_MAX_TOKENS:
155
+ parents.append(_make_parent(step_block, section.title, "procedure", section.page_hint, base_metadata))
156
+ else:
157
+ for part in _split_between_step_groups(step_block, PARENT_MAX_TOKENS):
158
+ parents.append(_make_parent(part, section.title, "procedure", section.page_hint, base_metadata))
159
+
160
+ # After-steps content
161
+ if after.strip():
162
+ for chunk in _split_by_paragraph(after, PARENT_MAX_TOKENS):
163
+ parents.append(_make_parent(chunk, section.title, chunk_type, section.page_hint, base_metadata))
164
+ else:
165
+ # No steps → split at paragraph boundaries
166
+ for chunk in _split_by_paragraph(text, PARENT_MAX_TOKENS):
167
+ parents.append(_make_parent(chunk, section.title, chunk_type, section.page_hint, base_metadata))
168
+
169
+ return parents
170
+
171
+
172
+ def _make_parent(content: str, title: str, chunk_type: str, page: int, metadata: dict) -> ParentChunk:
173
+ return ParentChunk(
174
+ content=content.strip(),
175
+ section_title=title,
176
+ chunk_type=chunk_type,
177
+ page_number=page,
178
+ token_count=count_tokens(content),
179
+ metadata=dict(metadata),
180
+ )
181
+
182
+
183
+ # ── Child Chunk Building ──────────────────────────────────────────────────────
184
+
185
+ def _build_child_chunks(parent: ParentChunk, base_metadata: dict) -> list[ChildChunk]:
186
+ total_tokens = count_tokens(parent.content)
187
+
188
+ # Small parent → single child = parent itself
189
+ if total_tokens <= CHILD_MAX_TOKENS:
190
+ child = _make_child(parent.content, parent.chunk_type, base_metadata, parent.section_title)
191
+ return [child]
192
+
193
+ # Split parent into children
194
+ raw_children = _split_by_sentence(parent.content, CHILD_MAX_TOKENS)
195
+
196
+ # Add overlap between siblings
197
+ children_with_overlap = _apply_overlap(raw_children)
198
+
199
+ return [
200
+ _make_child(c, parent.chunk_type, base_metadata, parent.section_title)
201
+ for c in children_with_overlap
202
+ ]
203
+
204
+
205
+ def _build_context_header(metadata: dict, section_title: str = "") -> str:
206
+ """Prepend product/section/source context for better embedding retrieval."""
207
+ if not CONTEXT_HEADERS:
208
+ return ""
209
+ product = metadata.get("product") or "IGEL"
210
+ section = section_title or metadata.get("section") or metadata.get("section_title") or ""
211
+ source = metadata.get("source") or metadata.get("file_path") or metadata.get("canonical_url") or ""
212
+ parts = [f"Product: {product}"]
213
+ if section:
214
+ parts.append(f"Section: {section}")
215
+ if source:
216
+ parts.append(f"Source: {source}")
217
+ return "\n".join(parts) + "\n\n"
218
+
219
+
220
+ def _make_child(content: str, chunk_type: str, metadata: dict, section_title: str = "") -> ChildChunk:
221
+ header = _build_context_header(metadata, section_title)
222
+ embed_body = f"{header}{content.strip()}" if header else content.strip()
223
+ stripped = content.strip()
224
+ token_count = count_tokens(embed_body)
225
+ has_steps = contains_steps(stripped)
226
+ has_table = "|" in stripped and stripped.count("|") > 4
227
+ actual_type = "procedure" if has_steps else ("table" if has_table else chunk_type)
228
+
229
+ validation = validate_chunk(stripped, token_count, actual_type, metadata)
230
+
231
+ return ChildChunk(
232
+ content=embed_body,
233
+ chunk_type=actual_type,
234
+ token_count=token_count,
235
+ contains_steps=has_steps,
236
+ has_table=has_table,
237
+ metadata=dict(metadata),
238
+ validation=validation,
239
+ )
240
+
241
+
242
+ # ── Splitting Helpers ─────────────────────────────────────────────────────────
243
+
244
+ def _split_by_paragraph(text: str, max_tokens: int) -> list[str]:
245
+ """Split text at double-newline boundaries without exceeding max_tokens."""
246
+ paragraphs = re.split(r"\n{2,}", text)
247
+ chunks: list[str] = []
248
+ current = ""
249
+
250
+ for para in paragraphs:
251
+ candidate = f"{current}\n\n{para}".strip() if current else para
252
+ if count_tokens(candidate) <= max_tokens:
253
+ current = candidate
254
+ else:
255
+ if current:
256
+ chunks.append(current.strip())
257
+ # Para itself too large — split by single newline
258
+ if count_tokens(para) > max_tokens:
259
+ chunks.extend(_split_by_line(para, max_tokens))
260
+ current = ""
261
+ else:
262
+ current = para
263
+
264
+ if current.strip():
265
+ chunks.append(current.strip())
266
+
267
+ return chunks or [text[:3000]] # hard fallback
268
+
269
+
270
+ def _split_by_line(text: str, max_tokens: int) -> list[str]:
271
+ lines = text.split("\n")
272
+ chunks: list[str] = []
273
+ current = ""
274
+
275
+ for line in lines:
276
+ candidate = f"{current}\n{line}".strip() if current else line
277
+ if count_tokens(candidate) <= max_tokens:
278
+ current = candidate
279
+ else:
280
+ if current:
281
+ chunks.append(current.strip())
282
+ current = line
283
+
284
+ if current.strip():
285
+ chunks.append(current.strip())
286
+
287
+ return chunks
288
+
289
+
290
+ def _split_by_sentence(text: str, max_tokens: int) -> list[str]:
291
+ """Split into child-sized units at sentence boundaries."""
292
+ # Split on sentence-ending punctuation followed by space + capital
293
+ sentences = re.split(r"(?<=[.!?;])\s+(?=[A-Z\d])", text)
294
+ chunks: list[str] = []
295
+ current = ""
296
+
297
+ for sent in sentences:
298
+ candidate = f"{current} {sent}".strip() if current else sent
299
+ if count_tokens(candidate) <= max_tokens:
300
+ current = candidate
301
+ else:
302
+ if current:
303
+ chunks.append(current.strip())
304
+ # Single sentence too long → split by token hard limit
305
+ if count_tokens(sent) > max_tokens:
306
+ chunks.extend(_split_by_token_limit(sent, max_tokens))
307
+ current = ""
308
+ else:
309
+ current = sent
310
+
311
+ if current.strip():
312
+ chunks.append(current.strip())
313
+
314
+ return chunks or [text]
315
+
316
+
317
+ def _split_by_token_limit(text: str, max_tokens: int) -> list[str]:
318
+ """Hard split by token count — last resort, used only for very long single sentences."""
319
+ tokens = _ENCODER.encode(text)
320
+ chunks = []
321
+ for i in range(0, len(tokens), max_tokens):
322
+ chunk_tokens = tokens[i:i + max_tokens]
323
+ chunks.append(_ENCODER.decode(chunk_tokens))
324
+ return chunks
325
+
326
+
327
+ def _apply_overlap(chunks: list[str]) -> list[str]:
328
+ """Add trailing tokens from previous chunk to start of next chunk."""
329
+ if len(chunks) <= 1:
330
+ return chunks
331
+
332
+ overlapped = [chunks[0]]
333
+ for i in range(1, len(chunks)):
334
+ prev_tokens = _ENCODER.encode(chunks[i - 1])
335
+ if len(prev_tokens) >= OVERLAP_TOKENS:
336
+ overlap_text = _ENCODER.decode(prev_tokens[-OVERLAP_TOKENS:])
337
+ overlapped.append(f"[...]\n{overlap_text}\n\n{chunks[i]}")
338
+ else:
339
+ overlapped.append(chunks[i])
340
+
341
+ return overlapped
342
+
343
+
344
+ def _extract_step_block(text: str) -> tuple[str, str, str]:
345
+ """
346
+ Locate the step sequence in text.
347
+ Returns (step_block, text_before, text_after).
348
+ If no steps found, returns ("", text, "").
349
+ """
350
+ # Find lines that are part of a step pattern
351
+ lines = text.split("\n")
352
+ step_indices = []
353
+
354
+ step_pattern = re.compile(
355
+ r"(^\s*\d+\.\s|^\s*Step\s*\d+|^\s*[a-z]\)\s|^\s*[•\-\*]\s)",
356
+ re.MULTILINE | re.IGNORECASE,
357
+ )
358
+
359
+ for i, line in enumerate(lines):
360
+ if step_pattern.match(line):
361
+ step_indices.append(i)
362
+
363
+ if len(step_indices) < 2:
364
+ return "", text, ""
365
+
366
+ start = step_indices[0]
367
+ end = step_indices[-1]
368
+
369
+ # Extend end to include lines belonging to the last step
370
+ while end + 1 < len(lines) and lines[end + 1].strip() and not _is_new_section(lines[end + 1]):
371
+ end += 1
372
+
373
+ before = "\n".join(lines[:start])
374
+ step_block = "\n".join(lines[start:end + 1])
375
+ after = "\n".join(lines[end + 1:])
376
+
377
+ return step_block, before, after
378
+
379
+
380
+ def _is_new_section(line: str) -> bool:
381
+ return bool(re.match(r"^#{1,4}\s|^[A-Z][A-Z\s]{5,}$", line))
382
+
383
+
384
+ def _split_between_step_groups(text: str, max_tokens: int) -> list[str]:
385
+ """Split a large steps block only at blank lines between step groups."""
386
+ groups = re.split(r"\n{2,}(?=\s*(?:\d+\.|Step\s*\d+))", text)
387
+ chunks: list[str] = []
388
+ current = ""
389
+
390
+ for group in groups:
391
+ candidate = f"{current}\n\n{group}".strip() if current else group
392
+ if count_tokens(candidate) <= max_tokens:
393
+ current = candidate
394
+ else:
395
+ if current:
396
+ chunks.append(current.strip())
397
+ current = group
398
+
399
+ if current.strip():
400
+ chunks.append(current.strip())
401
+
402
+ return chunks or [text]
@@ -0,0 +1,144 @@
1
+ """
2
+ Chunk validation — runs before any chunk is written to the database.
3
+ Hard failures block insertion. Warnings are logged but allowed through.
4
+ """
5
+ from __future__ import annotations
6
+ import re
7
+ from dataclasses import dataclass, field
8
+
9
+
10
+ STEP_PATTERNS = [
11
+ r"^\s*\d+\.\s",
12
+ r"^\s*Step\s*\d+",
13
+ r"^\s*[a-z]\)\s",
14
+ r"^\s*[•\-\*]\s",
15
+ r"^\s*\[\s*\]\s",
16
+ r"(^|\s)(First|Then|Next|Finally|After that|Once|Now),?\s",
17
+ ]
18
+
19
+ SENTENCE_ENDINGS = {".", ":", ";", "?", "!", ")", "]", '"', "|", ">"}
20
+
21
+ IGEL_PRODUCTS = ["UMS", "IGEL OS", "COSMOS", "Cloud Gateway", "ICG", "IMI", "IGEL Cloud"]
22
+
23
+ CHUNK_TOKEN_LIMITS = {
24
+ "procedure": (50, 750),
25
+ "concept": (50, 950),
26
+ "table": (30, 550),
27
+ "troubleshoot": (50, 650),
28
+ "configuration": (50, 750),
29
+ "api": (30, 650),
30
+ "default": (50, 950),
31
+ }
32
+
33
+
34
+ @dataclass
35
+ class ValidationResult:
36
+ passed: bool
37
+ warnings: list[str] = field(default_factory=list)
38
+ errors: list[str] = field(default_factory=list)
39
+
40
+
41
+ def validate_chunk(content: str, token_count: int, chunk_type: str, metadata: dict) -> ValidationResult:
42
+ errors: list[str] = []
43
+ warnings: list[str] = []
44
+
45
+ stripped = content.strip()
46
+ if not stripped:
47
+ errors.append("EMPTY_CHUNK")
48
+ return ValidationResult(passed=False, errors=errors)
49
+
50
+ # 1. Sentence completeness (last char must be a meaningful ending)
51
+ last_char = stripped[-1]
52
+ if last_char not in SENTENCE_ENDINGS:
53
+ errors.append(f"INCOMPLETE_END: chunk ends with '{last_char}' — likely cut mid-sentence")
54
+
55
+ # 2. Token size bounds
56
+ min_t, max_t = CHUNK_TOKEN_LIMITS.get(chunk_type, CHUNK_TOKEN_LIMITS["default"])
57
+ if token_count < min_t:
58
+ errors.append(f"TOO_SMALL: {token_count} tokens (min {min_t} for type '{chunk_type}')")
59
+ if token_count > max_t:
60
+ errors.append(f"TOO_LARGE: {token_count} tokens (max {max_t} for type '{chunk_type}')")
61
+
62
+ # 3. Step integrity — if steps detected, must have at least 2
63
+ step_matches = _find_steps(stripped)
64
+ if 0 < len(step_matches) < 2:
65
+ errors.append(
66
+ f"ORPHANED_STEP: only 1 step found ('{step_matches[0][:30]}') — "
67
+ "likely cut mid-procedure"
68
+ )
69
+
70
+ # 4. Table header presence — if table rows exist, header divider must too
71
+ if "|" in stripped and stripped.count("|") > 4:
72
+ if "---" not in stripped and "===" not in stripped:
73
+ warnings.append("TABLE_NO_HEADER: table rows present without markdown header divider")
74
+
75
+ # 5. Mixed product warning
76
+ products_found = [p for p in IGEL_PRODUCTS if p.lower() in stripped.lower()]
77
+ if len(products_found) > 1:
78
+ warnings.append(f"MIXED_PRODUCTS: chunk references {products_found} — consider splitting")
79
+
80
+ # 6. Cross-reference flag
81
+ if re.search(r"(see section|refer to section|as described in section|page \d+)", stripped, re.IGNORECASE):
82
+ warnings.append("CROSS_REF: chunk contains a reference to another section")
83
+
84
+ return ValidationResult(
85
+ passed=len(errors) == 0,
86
+ warnings=warnings,
87
+ errors=errors,
88
+ )
89
+
90
+
91
+ def validate_coverage(original_token_count: int, chunk_token_counts: list[int]) -> float:
92
+ """
93
+ Ensure chunking didn't lose content.
94
+ Returns coverage ratio. Raises if below 90%.
95
+ """
96
+ if original_token_count == 0:
97
+ return 1.0
98
+ chunked_total = sum(chunk_token_counts)
99
+ ratio = chunked_total / original_token_count
100
+ if ratio < 0.90:
101
+ raise ValueError(
102
+ f"Coverage too low: {ratio:.1%} — {(1 - ratio) * 100:.1f}% of content lost during chunking"
103
+ )
104
+ return ratio
105
+
106
+
107
+ def contains_steps(text: str) -> bool:
108
+ for pattern in STEP_PATTERNS:
109
+ if re.search(pattern, text, re.MULTILINE | re.IGNORECASE):
110
+ return True
111
+ return False
112
+
113
+
114
+ def _find_steps(text: str) -> list[str]:
115
+ matches = []
116
+ for pattern in STEP_PATTERNS[:5]: # numbered/bullet patterns only
117
+ found = re.findall(pattern, text, re.MULTILINE | re.IGNORECASE)
118
+ matches.extend(found)
119
+ return matches
120
+
121
+
122
+ def classify_chunk_type(text: str, section_title: str = "") -> str:
123
+ title_lower = section_title.lower()
124
+ text_lower = text.lower()
125
+
126
+ if contains_steps(text):
127
+ return "procedure"
128
+ if any(w in title_lower for w in ["troubleshoot", "error", "fail", "issue", "problem", "debug"]):
129
+ return "troubleshoot"
130
+ if any(w in title_lower for w in ["configure", "setup", "install", "enable", "disable", "settings"]):
131
+ return "configuration"
132
+ if any(w in text_lower for w in ["api", "endpoint", "request", "response", "curl", "http", "json"]):
133
+ return "api"
134
+ if "|" in text and text.count("|") > 4:
135
+ return "table"
136
+ return "concept"
137
+
138
+
139
+ def detect_product(text: str, file_name: str = "") -> str:
140
+ combined = (text + " " + file_name).lower()
141
+ for product in IGEL_PRODUCTS:
142
+ if product.lower() in combined:
143
+ return product
144
+ return "IGEL" # default — everything is IGEL-related
@@ -0,0 +1 @@
1
+ """Command-line entry points."""