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.
- package/dist/cli/index.js +38 -0
- package/dist/mcp/server.js +104 -0
- package/knowledge_base/__init__.py +3 -0
- package/knowledge_base/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/__pycache__/confidence.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/config.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/config.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/config.cpython-38.pyc +0 -0
- package/knowledge_base/__pycache__/context_fusion.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/context_fusion.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/feedback_analytics.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/feedback_applier.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/feedback_cli.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/feedback_curator.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/feedback_handler.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/generate.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/generate_qcapps134.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/input_classifier.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/input_classifier.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/kg_extractor.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/knowledge_graph.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/manual_tc_parser.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/manual_tc_parser.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/precedence.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/prompts.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/prompts.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/reranker.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/reranker.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/retrieval_debug.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/retrieval_debug.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/retriever.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/retriever.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/server.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/tc_transformer.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/tc_transformer.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/test_generator.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/test_generator.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__init__.py +6 -0
- package/knowledge_base/agents/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/fusion_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/fusion_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/graph.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/graph.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/healing_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/intent_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/intent_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/kg_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/kg_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/memory_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/memory_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/rca_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/reflection_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/reflection_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/regression_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/retrieval_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/retrieval_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/script_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/script_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/state.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/state.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/temporal_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/temporal_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/validation_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/validation_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/automation_design_agent.py +123 -0
- package/knowledge_base/agents/code_intelligence_agent.py +147 -0
- package/knowledge_base/agents/confidence_agent.py +94 -0
- package/knowledge_base/agents/execution_validation_agent.py +129 -0
- package/knowledge_base/agents/feedback_agent.py +56 -0
- package/knowledge_base/agents/fusion_agent.py +113 -0
- package/knowledge_base/agents/graph.py +152 -0
- package/knowledge_base/agents/healing_agent.py +62 -0
- package/knowledge_base/agents/intent_agent.py +76 -0
- package/knowledge_base/agents/kg_agent.py +91 -0
- package/knowledge_base/agents/memory_agent.py +62 -0
- package/knowledge_base/agents/rca_agent.py +53 -0
- package/knowledge_base/agents/reflection_agent.py +62 -0
- package/knowledge_base/agents/regression_agent.py +81 -0
- package/knowledge_base/agents/requirement_agent.py +71 -0
- package/knowledge_base/agents/retrieval_agent.py +66 -0
- package/knowledge_base/agents/scenario_agent.py +81 -0
- package/knowledge_base/agents/script_agent.py +121 -0
- package/knowledge_base/agents/script_generator_agent.py +77 -0
- package/knowledge_base/agents/state.py +53 -0
- package/knowledge_base/agents/temporal_agent.py +48 -0
- package/knowledge_base/agents/testcase_agent.py +111 -0
- package/knowledge_base/agents/validation_agent.py +49 -0
- package/knowledge_base/agents/workflow_engine.py +87 -0
- package/knowledge_base/alembic/README.md +104 -0
- package/knowledge_base/alembic/__pycache__/env.cpython-37.pyc +0 -0
- package/knowledge_base/alembic/__pycache__/env.cpython-38.pyc +0 -0
- package/knowledge_base/alembic/env.py +76 -0
- package/knowledge_base/alembic/script.py.mako +5 -0
- package/knowledge_base/alembic/versions/.gitkeep +26 -0
- package/knowledge_base/alembic/versions/001_phase0_feedback.py +122 -0
- package/knowledge_base/alembic/versions/002_feedback_promotions.py +60 -0
- package/knowledge_base/alembic/versions/003_enterprise_hardening.py +74 -0
- package/knowledge_base/alembic/versions/__pycache__/001_phase0_feedback.cpython-37.pyc +0 -0
- package/knowledge_base/alembic/versions/__pycache__/001_phase0_feedback.cpython-38.pyc +0 -0
- package/knowledge_base/alembic/versions/__pycache__/002_feedback_promotions.cpython-37.pyc +0 -0
- package/knowledge_base/alembic/versions/__pycache__/002_feedback_promotions.cpython-38.pyc +0 -0
- package/knowledge_base/api/__init__.py +1 -0
- package/knowledge_base/api/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/api/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/api/__pycache__/middleware.cpython-38.pyc +0 -0
- package/knowledge_base/api/__pycache__/server.cpython-37.pyc +0 -0
- package/knowledge_base/api/__pycache__/server.cpython-38.pyc +0 -0
- package/knowledge_base/api/middleware.py +78 -0
- package/knowledge_base/api/server.py +778 -0
- package/knowledge_base/chunking/__init__.py +0 -0
- package/knowledge_base/chunking/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/chunking/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/chunking/__pycache__/chunker.cpython-37.pyc +0 -0
- package/knowledge_base/chunking/__pycache__/chunker.cpython-38.pyc +0 -0
- package/knowledge_base/chunking/__pycache__/validators.cpython-37.pyc +0 -0
- package/knowledge_base/chunking/__pycache__/validators.cpython-38.pyc +0 -0
- package/knowledge_base/chunking/chunker.py +402 -0
- package/knowledge_base/chunking/validators.py +144 -0
- package/knowledge_base/cli/__init__.py +1 -0
- package/knowledge_base/cli/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/cli/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/cli/__pycache__/feedback_cli.cpython-37.pyc +0 -0
- package/knowledge_base/cli/__pycache__/feedback_cli.cpython-38.pyc +0 -0
- package/knowledge_base/cli/__pycache__/generate.cpython-37.pyc +0 -0
- package/knowledge_base/cli/__pycache__/generate.cpython-38.pyc +0 -0
- package/knowledge_base/cli/__pycache__/generate_qcapps134.cpython-37.pyc +0 -0
- package/knowledge_base/cli/feedback_cli.py +121 -0
- package/knowledge_base/cli/generate.py +308 -0
- package/knowledge_base/cli/generate_qcapps134.py +391 -0
- package/knowledge_base/config/.env +100 -0
- package/knowledge_base/config/.env.example +75 -0
- package/knowledge_base/config/__init__.py +5 -0
- package/knowledge_base/config/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/config/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/config/__pycache__/settings.cpython-37.pyc +0 -0
- package/knowledge_base/config/__pycache__/settings.cpython-38.pyc +0 -0
- package/knowledge_base/config/settings.py +280 -0
- package/knowledge_base/connectors/__init__.py +5 -0
- package/knowledge_base/connectors/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/base.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/base.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/bitbucket_connector.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/bitbucket_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/community_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/confluence_connector.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/confluence_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/jira_connector.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/jira_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/kb_igel_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/playwright_fetcher.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/query_bridge.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/search_backends.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/serviceNow_connector.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/serviceNow_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/sync_scheduler.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/sync_scheduler.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/teamcity_connector.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/teamcity_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/testRail_connector.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/testRail_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/web_fetcher.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/base.py +44 -0
- package/knowledge_base/connectors/bitbucket_connector.py +25 -0
- package/knowledge_base/connectors/community_connector.py +112 -0
- package/knowledge_base/connectors/confluence_connector.py +165 -0
- package/knowledge_base/connectors/jira_connector.py +174 -0
- package/knowledge_base/connectors/kb_igel_connector.py +145 -0
- package/knowledge_base/connectors/playwright_fetcher.py +50 -0
- package/knowledge_base/connectors/query_bridge.py +170 -0
- package/knowledge_base/connectors/search_backends.py +368 -0
- package/knowledge_base/connectors/serviceNow_connector.py +25 -0
- package/knowledge_base/connectors/sync_scheduler.py +111 -0
- package/knowledge_base/connectors/teamcity_connector.py +25 -0
- package/knowledge_base/connectors/testRail_connector.py +25 -0
- package/knowledge_base/connectors/web_fetcher.py +280 -0
- package/knowledge_base/core/__init__.py +8 -0
- package/knowledge_base/core/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/core/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/core/__pycache__/cache.cpython-38.pyc +0 -0
- package/knowledge_base/core/__pycache__/circuit_breaker.cpython-38.pyc +0 -0
- package/knowledge_base/core/__pycache__/confidence.cpython-37.pyc +0 -0
- package/knowledge_base/core/__pycache__/confidence.cpython-38.pyc +0 -0
- package/knowledge_base/core/__pycache__/logging_setup.cpython-38.pyc +0 -0
- package/knowledge_base/core/__pycache__/metrics.cpython-38.pyc +0 -0
- package/knowledge_base/core/__pycache__/precedence.cpython-37.pyc +0 -0
- package/knowledge_base/core/__pycache__/precedence.cpython-38.pyc +0 -0
- package/knowledge_base/core/__pycache__/request_context.cpython-38.pyc +0 -0
- package/knowledge_base/core/cache.py +98 -0
- package/knowledge_base/core/circuit_breaker.py +33 -0
- package/knowledge_base/core/confidence.py +254 -0
- package/knowledge_base/core/logging_setup.py +39 -0
- package/knowledge_base/core/metrics.py +63 -0
- package/knowledge_base/core/precedence.py +354 -0
- package/knowledge_base/core/request_context.py +54 -0
- package/knowledge_base/db/__init__.py +0 -0
- package/knowledge_base/db/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/db/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/db/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/db/__pycache__/client.cpython-314.pyc +0 -0
- package/knowledge_base/db/__pycache__/client.cpython-37.pyc +0 -0
- package/knowledge_base/db/__pycache__/client.cpython-38.pyc +0 -0
- package/knowledge_base/db/__pycache__/connector_sync.cpython-38.pyc +0 -0
- package/knowledge_base/db/__pycache__/feedback_schema.cpython-37.pyc +0 -0
- package/knowledge_base/db/__pycache__/feedback_schema.cpython-38.pyc +0 -0
- package/knowledge_base/db/__pycache__/kg_introspect.cpython-314.pyc +0 -0
- package/knowledge_base/db/__pycache__/kg_introspect.cpython-37.pyc +0 -0
- package/knowledge_base/db/__pycache__/kg_introspect.cpython-38.pyc +0 -0
- package/knowledge_base/db/__pycache__/schema.cpython-37.pyc +0 -0
- package/knowledge_base/db/__pycache__/schema.cpython-38.pyc +0 -0
- package/knowledge_base/db/client.py +100 -0
- package/knowledge_base/db/connector_sync.py +43 -0
- package/knowledge_base/db/feedback_schema.py +548 -0
- package/knowledge_base/db/igel_schema.py +150 -0
- package/knowledge_base/db/kg_introspect.py +116 -0
- package/knowledge_base/db/migrations.py +22 -0
- package/knowledge_base/db/schema.py +649 -0
- package/knowledge_base/deploy/Dockerfile +23 -0
- package/knowledge_base/deploy/feedback_automation.sh +18 -0
- package/knowledge_base/deploy/firecrawl-docker.sh +50 -0
- package/knowledge_base/deploy/igel-testgen.service +26 -0
- package/knowledge_base/deploy/install_reranker.sh +92 -0
- package/knowledge_base/deploy/requirements.txt +68 -0
- package/knowledge_base/deploy/run_server.sh +35 -0
- package/knowledge_base/docs/TEST_GENERATION_GUIDE.md +174 -0
- package/knowledge_base/embeddings/__init__.py +0 -0
- package/knowledge_base/embeddings/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/embeddings/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/embeddings/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-314.pyc +0 -0
- package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-37.pyc +0 -0
- package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-38.pyc +0 -0
- package/knowledge_base/embeddings/azure_embedder.py +99 -0
- package/knowledge_base/eval/__init__.py +33 -0
- package/knowledge_base/eval/__main__.py +172 -0
- package/knowledge_base/eval/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/eval/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/eval/__pycache__/__main__.cpython-37.pyc +0 -0
- package/knowledge_base/eval/__pycache__/baseline.cpython-37.pyc +0 -0
- package/knowledge_base/eval/__pycache__/baseline.cpython-38.pyc +0 -0
- package/knowledge_base/eval/__pycache__/harness.cpython-37.pyc +0 -0
- package/knowledge_base/eval/__pycache__/harness.cpython-38.pyc +0 -0
- package/knowledge_base/eval/__pycache__/scorers.cpython-37.pyc +0 -0
- package/knowledge_base/eval/__pycache__/scorers.cpython-38.pyc +0 -0
- package/knowledge_base/eval/baseline.py +213 -0
- package/knowledge_base/eval/harness.py +400 -0
- package/knowledge_base/eval/queries_50.json +302 -0
- package/knowledge_base/eval/scorers.py +494 -0
- package/knowledge_base/exemplars/README.md +7 -0
- package/knowledge_base/exemplars/__pycache__/pytest_reference_snippet.cpython-37.pyc +0 -0
- package/knowledge_base/exemplars/gold_manual_concise_snippet.md +8 -0
- package/knowledge_base/exemplars/gold_manual_detailed_snippet.md +7 -0
- package/knowledge_base/exemplars/pytest_reference_snippet.py +37 -0
- package/knowledge_base/feedback/__init__.py +10 -0
- package/knowledge_base/feedback/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/analytics.cpython-37.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/analytics.cpython-38.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/applier.cpython-37.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/applier.cpython-38.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/curator.cpython-37.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/curator.cpython-38.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/handler.cpython-37.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/handler.cpython-38.pyc +0 -0
- package/knowledge_base/feedback/analytics.py +72 -0
- package/knowledge_base/feedback/applier.py +93 -0
- package/knowledge_base/feedback/curator.py +49 -0
- package/knowledge_base/feedback/handler.py +151 -0
- package/knowledge_base/generation/__init__.py +5 -0
- package/knowledge_base/generation/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/generation/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/generation/__pycache__/input_classifier.cpython-37.pyc +0 -0
- package/knowledge_base/generation/__pycache__/input_classifier.cpython-38.pyc +0 -0
- package/knowledge_base/generation/__pycache__/manual_tc_parser.cpython-37.pyc +0 -0
- package/knowledge_base/generation/__pycache__/manual_tc_parser.cpython-38.pyc +0 -0
- package/knowledge_base/generation/__pycache__/prompts.cpython-37.pyc +0 -0
- package/knowledge_base/generation/__pycache__/prompts.cpython-38.pyc +0 -0
- package/knowledge_base/generation/__pycache__/tc_transformer.cpython-37.pyc +0 -0
- package/knowledge_base/generation/__pycache__/test_generator.cpython-37.pyc +0 -0
- package/knowledge_base/generation/__pycache__/test_generator.cpython-38.pyc +0 -0
- package/knowledge_base/generation/input_classifier.py +148 -0
- package/knowledge_base/generation/manual_tc_parser.py +180 -0
- package/knowledge_base/generation/prompts.py +755 -0
- package/knowledge_base/generation/tc_transformer.py +368 -0
- package/knowledge_base/generation/test_generator.py +553 -0
- package/knowledge_base/graph_db/__init__.py +5 -0
- package/knowledge_base/graph_db/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/graphiti_client.cpython-37.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/kg_traversal.cpython-37.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/migrate_kg.cpython-37.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/neo4j_client.cpython-37.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/neo4j_client.cpython-38.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/schema.cpython-37.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/temporal_ingest.cpython-37.pyc +0 -0
- package/knowledge_base/graph_db/graphiti_client.py +127 -0
- package/knowledge_base/graph_db/kg_traversal.py +192 -0
- package/knowledge_base/graph_db/migrate_kg.py +93 -0
- package/knowledge_base/graph_db/neo4j_client.py +116 -0
- package/knowledge_base/graph_db/schema.py +22 -0
- package/knowledge_base/graph_db/temporal_ingest.py +59 -0
- package/knowledge_base/ingestion/__init__.py +1 -0
- package/knowledge_base/ingestion/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/ingest.cpython-37.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/ingest.cpython-38.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/ingest_images.cpython-37.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/ingest_images.cpython-38.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/kg_extractor.cpython-37.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/kg_extractor.cpython-38.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/web_ingest.cpython-38.pyc +0 -0
- package/knowledge_base/ingestion/ingest.py +346 -0
- package/knowledge_base/ingestion/ingest_images.py +330 -0
- package/knowledge_base/ingestion/kg_extractor.py +358 -0
- package/knowledge_base/ingestion/web_ingest.py +83 -0
- package/knowledge_base/intelligence/__init__.py +1 -0
- package/knowledge_base/intelligence/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/intelligence/__pycache__/execution_ingest.cpython-37.pyc +0 -0
- package/knowledge_base/intelligence/__pycache__/predictive_engine.cpython-37.pyc +0 -0
- package/knowledge_base/intelligence/__pycache__/risk_scorer.cpython-37.pyc +0 -0
- package/knowledge_base/intelligence/execution_ingest.py +34 -0
- package/knowledge_base/intelligence/predictive_engine.py +7 -0
- package/knowledge_base/intelligence/risk_scorer.py +8 -0
- package/knowledge_base/memory/__init__.py +6 -0
- package/knowledge_base/memory/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/memory/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/memory/__pycache__/cognee_client.cpython-37.pyc +0 -0
- package/knowledge_base/memory/__pycache__/cognee_client.cpython-38.pyc +0 -0
- package/knowledge_base/memory/__pycache__/ingest_memory.cpython-37.pyc +0 -0
- package/knowledge_base/memory/__pycache__/memory_types.cpython-37.pyc +0 -0
- package/knowledge_base/memory/__pycache__/memory_types.cpython-38.pyc +0 -0
- package/knowledge_base/memory/cognee_client.py +164 -0
- package/knowledge_base/memory/ingest_memory.py +60 -0
- package/knowledge_base/memory/memory_types.py +23 -0
- package/knowledge_base/models/test_requirement.py +84 -0
- package/knowledge_base/parsers/__init__.py +0 -0
- package/knowledge_base/parsers/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/csv_parser.cpython-37.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/csv_parser.cpython-38.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/doc_parser.cpython-37.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/doc_parser.cpython-38.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/html_parser.cpython-38.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/image_extractor.cpython-37.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/image_extractor.cpython-38.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/markdown_parser.cpython-37.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/markdown_parser.cpython-38.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/pdf_parser.cpython-37.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/pdf_parser.cpython-38.pyc +0 -0
- package/knowledge_base/parsers/csv_parser.py +126 -0
- package/knowledge_base/parsers/doc_parser.py +176 -0
- package/knowledge_base/parsers/html_parser.py +151 -0
- package/knowledge_base/parsers/image_extractor.py +370 -0
- package/knowledge_base/parsers/markdown_parser.py +116 -0
- package/knowledge_base/parsers/pdf_parser.py +178 -0
- package/knowledge_base/retrieval/__init__.py +1 -0
- package/knowledge_base/retrieval/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/context_fusion.cpython-37.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/context_fusion.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/knowledge_graph.cpython-37.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/knowledge_graph.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/reranker.cpython-37.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/reranker.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/retrieval_debug.cpython-37.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/retrieval_debug.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/retriever.cpython-37.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/retriever.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/unified_query.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/web_retriever.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/context_fusion.py +264 -0
- package/knowledge_base/retrieval/knowledge_graph.py +267 -0
- package/knowledge_base/retrieval/reranker.py +308 -0
- package/knowledge_base/retrieval/retrieval_debug.py +69 -0
- package/knowledge_base/retrieval/retriever.py +885 -0
- package/knowledge_base/retrieval/unified_query.py +189 -0
- package/knowledge_base/retrieval/web_retriever.py +337 -0
- package/knowledge_base/validators/__init__.py +1 -0
- package/knowledge_base/validators/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/validators/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/validators/__pycache__/step_fidelity.cpython-314.pyc +0 -0
- package/knowledge_base/validators/__pycache__/step_fidelity.cpython-37.pyc +0 -0
- package/knowledge_base/validators/step_fidelity.py +88 -0
- package/knowledge_base/vision/__init__.py +0 -0
- package/knowledge_base/vision/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/vision/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/vision/__pycache__/image_describer.cpython-37.pyc +0 -0
- package/knowledge_base/vision/__pycache__/image_describer.cpython-38.pyc +0 -0
- package/knowledge_base/vision/image_describer.py +202 -0
- package/package.json +31 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Source Precedence Rules for IGEL KB System
|
|
3
|
+
===========================================
|
|
4
|
+
Enforces trust hierarchy when conflicting information appears in multiple sources.
|
|
5
|
+
|
|
6
|
+
Precedence Order (Highest → Lowest):
|
|
7
|
+
1. Gold TC (manual, reviewed, canonical test cases)
|
|
8
|
+
2. Jira TC export (test cases from Jira, may be outdated)
|
|
9
|
+
3. Confluence (team documentation, wiki pages)
|
|
10
|
+
4. Official IGEL docs (reference manuals, admin guides)
|
|
11
|
+
5. Screenshots (UI images with OCR)
|
|
12
|
+
6. Execution history (past test runs, logs)
|
|
13
|
+
|
|
14
|
+
Rules:
|
|
15
|
+
- Higher precedence sources override lower ones for conflicting info
|
|
16
|
+
- When merging: use highest-precedence source for each fact
|
|
17
|
+
- Surface source attribution in output (which doc provided which info)
|
|
18
|
+
- Flag conflicts for human review if precedence gap is small
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class SourceType:
|
|
28
|
+
"""Source type with precedence rank."""
|
|
29
|
+
name: str
|
|
30
|
+
rank: int # Lower number = higher precedence
|
|
31
|
+
description: str
|
|
32
|
+
|
|
33
|
+
def __lt__(self, other: SourceType) -> bool:
|
|
34
|
+
return self.rank < other.rank
|
|
35
|
+
|
|
36
|
+
def __le__(self, other: SourceType) -> bool:
|
|
37
|
+
return self.rank <= other.rank
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Define source types (precedence order)
|
|
41
|
+
SOURCE_TYPES = {
|
|
42
|
+
"gold_tc": SourceType(
|
|
43
|
+
name="gold_tc",
|
|
44
|
+
rank=1,
|
|
45
|
+
description="Gold Test Case (manual, reviewed, canonical)",
|
|
46
|
+
),
|
|
47
|
+
"jira_tc": SourceType(
|
|
48
|
+
name="jira_tc",
|
|
49
|
+
rank=2,
|
|
50
|
+
description="Jira Test Case Export",
|
|
51
|
+
),
|
|
52
|
+
"confluence": SourceType(
|
|
53
|
+
name="confluence",
|
|
54
|
+
rank=3,
|
|
55
|
+
description="Confluence Documentation",
|
|
56
|
+
),
|
|
57
|
+
"igel_docs": SourceType(
|
|
58
|
+
name="igel_docs",
|
|
59
|
+
rank=4,
|
|
60
|
+
description="Official IGEL Documentation",
|
|
61
|
+
),
|
|
62
|
+
"kb_web": SourceType(
|
|
63
|
+
name="kb_web",
|
|
64
|
+
rank=4,
|
|
65
|
+
description="IGEL Knowledge Base (kb.igel.com)",
|
|
66
|
+
),
|
|
67
|
+
"community": SourceType(
|
|
68
|
+
name="community",
|
|
69
|
+
rank=5,
|
|
70
|
+
description="IGEL Community / Forum Posts",
|
|
71
|
+
),
|
|
72
|
+
"screenshot": SourceType(
|
|
73
|
+
name="screenshot",
|
|
74
|
+
rank=6,
|
|
75
|
+
description="Screenshot with OCR",
|
|
76
|
+
),
|
|
77
|
+
"execution_history": SourceType(
|
|
78
|
+
name="execution_history",
|
|
79
|
+
rank=7,
|
|
80
|
+
description="Test Execution History",
|
|
81
|
+
),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def infer_source_type(chunk_metadata: dict) -> SourceType:
|
|
86
|
+
"""
|
|
87
|
+
Infer source type from chunk metadata.
|
|
88
|
+
|
|
89
|
+
Looks at:
|
|
90
|
+
- metadata.product (e.g., "IGEL-GOLD-TC", "IGEL-JIRA", "IGEL-CONFLUENCE")
|
|
91
|
+
- metadata.file_path
|
|
92
|
+
- metadata.source_type (if explicitly tagged)
|
|
93
|
+
"""
|
|
94
|
+
# Check explicit source_type tag
|
|
95
|
+
if "source_type" in chunk_metadata:
|
|
96
|
+
source_name = chunk_metadata["source_type"].lower()
|
|
97
|
+
if source_name in SOURCE_TYPES:
|
|
98
|
+
return SOURCE_TYPES[source_name]
|
|
99
|
+
product = chunk_metadata.get("product", "").lower()
|
|
100
|
+
if "gold" in product or "canonical" in product:
|
|
101
|
+
return SOURCE_TYPES["gold_tc"]
|
|
102
|
+
elif "jira" in product:
|
|
103
|
+
return SOURCE_TYPES["jira_tc"]
|
|
104
|
+
elif "confluence" in product or "wiki" in product:
|
|
105
|
+
return SOURCE_TYPES["confluence"]
|
|
106
|
+
elif "igel" in product or "docs" in product or "manual" in product:
|
|
107
|
+
return SOURCE_TYPES["igel_docs"]
|
|
108
|
+
elif "kb_web" in product or "kb.igel" in product:
|
|
109
|
+
return SOURCE_TYPES["kb_web"]
|
|
110
|
+
elif "community" in product or "forum" in product:
|
|
111
|
+
return SOURCE_TYPES["community"]
|
|
112
|
+
elif "screenshot" in product or "image" in product:
|
|
113
|
+
return SOURCE_TYPES["screenshot"]
|
|
114
|
+
elif "execution" in product or "history" in product or "log" in product:
|
|
115
|
+
return SOURCE_TYPES["execution_history"]
|
|
116
|
+
|
|
117
|
+
# Check file path
|
|
118
|
+
file_path = chunk_metadata.get("file_path", "").lower()
|
|
119
|
+
if "gold" in file_path:
|
|
120
|
+
return SOURCE_TYPES["gold_tc"]
|
|
121
|
+
elif "jira" in file_path:
|
|
122
|
+
return SOURCE_TYPES["jira_tc"]
|
|
123
|
+
elif "confluence" in file_path or "wiki" in file_path:
|
|
124
|
+
return SOURCE_TYPES["confluence"]
|
|
125
|
+
elif "manual" in file_path or "guide" in file_path or "reference" in file_path:
|
|
126
|
+
return SOURCE_TYPES["igel_docs"]
|
|
127
|
+
elif "kb.igel" in file_path or file_path.startswith("web:kb"):
|
|
128
|
+
return SOURCE_TYPES["kb_web"]
|
|
129
|
+
elif "community" in file_path or file_path.startswith("web:community"):
|
|
130
|
+
return SOURCE_TYPES["community"]
|
|
131
|
+
elif "screenshot" in file_path or "image" in file_path:
|
|
132
|
+
return SOURCE_TYPES["screenshot"]
|
|
133
|
+
elif "execution" in file_path or "history" in file_path:
|
|
134
|
+
return SOURCE_TYPES["execution_history"]
|
|
135
|
+
|
|
136
|
+
# Default to IGEL docs (safest middle ground)
|
|
137
|
+
return SOURCE_TYPES["igel_docs"]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass
|
|
141
|
+
class RankedChunk:
|
|
142
|
+
"""Chunk with source precedence metadata."""
|
|
143
|
+
chunk: Any # Original chunk object
|
|
144
|
+
source_type: SourceType
|
|
145
|
+
retrieval_score: float # RRF or rerank score
|
|
146
|
+
content: str
|
|
147
|
+
metadata: dict
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def composite_rank(self) -> tuple[int, float]:
|
|
151
|
+
"""
|
|
152
|
+
Composite ranking key: (source_rank, -retrieval_score).
|
|
153
|
+
Sorts by source precedence first, then retrieval score (higher better).
|
|
154
|
+
"""
|
|
155
|
+
return (self.source_type.rank, -self.retrieval_score)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def rank_chunks_by_precedence(
|
|
159
|
+
chunks: list,
|
|
160
|
+
retrieval_scores: dict | None = None,
|
|
161
|
+
) -> list[RankedChunk]:
|
|
162
|
+
"""
|
|
163
|
+
Rank chunks by source precedence, then retrieval score.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
chunks: List of chunk objects (must have .metadata attribute)
|
|
167
|
+
retrieval_scores: Optional dict mapping chunk ID to retrieval score
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
List of RankedChunk objects, sorted by precedence
|
|
171
|
+
"""
|
|
172
|
+
ranked = []
|
|
173
|
+
|
|
174
|
+
for chunk in chunks:
|
|
175
|
+
# Extract metadata
|
|
176
|
+
metadata = getattr(chunk, 'metadata', {})
|
|
177
|
+
if isinstance(metadata, str):
|
|
178
|
+
import json
|
|
179
|
+
try:
|
|
180
|
+
metadata = json.loads(metadata)
|
|
181
|
+
except:
|
|
182
|
+
metadata = {}
|
|
183
|
+
|
|
184
|
+
# Infer source type
|
|
185
|
+
source_type = infer_source_type(metadata)
|
|
186
|
+
|
|
187
|
+
# Get retrieval score
|
|
188
|
+
chunk_id = getattr(chunk, 'id', None)
|
|
189
|
+
retrieval_score = 0.0
|
|
190
|
+
if retrieval_scores and chunk_id:
|
|
191
|
+
retrieval_score = retrieval_scores.get(str(chunk_id), 0.0)
|
|
192
|
+
elif hasattr(chunk, 'rrf_score'):
|
|
193
|
+
retrieval_score = chunk.rrf_score
|
|
194
|
+
elif hasattr(chunk, 'rerank_score'):
|
|
195
|
+
retrieval_score = chunk.rerank_score
|
|
196
|
+
|
|
197
|
+
# Get content
|
|
198
|
+
content = getattr(chunk, 'content', '') or getattr(chunk, 'parent_content', '') or getattr(chunk, 'child_content', '')
|
|
199
|
+
|
|
200
|
+
ranked.append(RankedChunk(
|
|
201
|
+
chunk=chunk,
|
|
202
|
+
source_type=source_type,
|
|
203
|
+
retrieval_score=retrieval_score,
|
|
204
|
+
content=content,
|
|
205
|
+
metadata=metadata,
|
|
206
|
+
))
|
|
207
|
+
|
|
208
|
+
# Sort by composite rank (source precedence, then retrieval score)
|
|
209
|
+
ranked.sort(key=lambda rc: rc.composite_rank)
|
|
210
|
+
|
|
211
|
+
return ranked
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def apply_precedence_filter(
|
|
215
|
+
chunks: list,
|
|
216
|
+
top_k: int = 10,
|
|
217
|
+
retrieval_scores: dict | None = None,
|
|
218
|
+
) -> tuple[list, dict]:
|
|
219
|
+
"""
|
|
220
|
+
Apply source precedence to filter and rank chunks.
|
|
221
|
+
|
|
222
|
+
Strategy:
|
|
223
|
+
1. Rank all chunks by precedence
|
|
224
|
+
2. Select top K chunks
|
|
225
|
+
3. Within each precedence tier, prefer higher retrieval scores
|
|
226
|
+
4. Return filtered list + attribution metadata
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
(filtered_chunks, attribution_metadata)
|
|
230
|
+
|
|
231
|
+
attribution_metadata: dict with source breakdown
|
|
232
|
+
"""
|
|
233
|
+
# Rank chunks
|
|
234
|
+
ranked = rank_chunks_by_precedence(chunks, retrieval_scores)
|
|
235
|
+
|
|
236
|
+
# Take top K
|
|
237
|
+
top_chunks = ranked[:top_k]
|
|
238
|
+
|
|
239
|
+
# Build attribution metadata
|
|
240
|
+
source_counts = {}
|
|
241
|
+
for rc in top_chunks:
|
|
242
|
+
source_name = rc.source_type.name
|
|
243
|
+
source_counts[source_name] = source_counts.get(source_name, 0) + 1
|
|
244
|
+
|
|
245
|
+
attribution = {
|
|
246
|
+
"total_chunks": len(top_chunks),
|
|
247
|
+
"source_breakdown": source_counts,
|
|
248
|
+
"highest_precedence": top_chunks[0].source_type.name if top_chunks else None,
|
|
249
|
+
"precedence_order": [rc.source_type.name for rc in top_chunks],
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# Extract original chunks
|
|
253
|
+
filtered = [rc.chunk for rc in top_chunks]
|
|
254
|
+
|
|
255
|
+
return filtered, attribution
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def resolve_conflict(
|
|
259
|
+
sources: list[tuple[SourceType, Any]],
|
|
260
|
+
) -> tuple[SourceType, Any] | None:
|
|
261
|
+
"""
|
|
262
|
+
Resolve conflicting information by source precedence (lowest rank wins).
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
sources: List of (SourceType, value) tuples from different sources.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Winning (SourceType, value) or None if empty.
|
|
269
|
+
"""
|
|
270
|
+
if not sources:
|
|
271
|
+
return None
|
|
272
|
+
return min(sources, key=lambda pair: pair[0].rank)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def detect_precedence_conflicts(
|
|
276
|
+
chunks: list,
|
|
277
|
+
conflict_threshold: int = 2,
|
|
278
|
+
) -> list[dict]:
|
|
279
|
+
"""
|
|
280
|
+
Detect conflicts where similar information appears in sources
|
|
281
|
+
with different precedence levels.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
chunks: List of chunks
|
|
285
|
+
conflict_threshold: Max precedence rank gap to consider conflict
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
List of conflict reports (for human review)
|
|
289
|
+
"""
|
|
290
|
+
ranked = rank_chunks_by_precedence(chunks)
|
|
291
|
+
conflicts = []
|
|
292
|
+
|
|
293
|
+
# Group by similar content (simplified: just check for registry keys/UI paths)
|
|
294
|
+
import re
|
|
295
|
+
|
|
296
|
+
# Extract entities from each chunk
|
|
297
|
+
for i, rc1 in enumerate(ranked):
|
|
298
|
+
entities_1 = set(re.findall(r'app\.[a-z0-9_.]+', rc1.content.lower()))
|
|
299
|
+
entities_1.update(re.findall(r'TC Setup[^>\n]*(?:>[^>\n]*)*', rc1.content, re.I))
|
|
300
|
+
|
|
301
|
+
for rc2 in ranked[i+1:]:
|
|
302
|
+
# Check precedence gap
|
|
303
|
+
rank_gap = abs(rc1.source_type.rank - rc2.source_type.rank)
|
|
304
|
+
if rank_gap > conflict_threshold:
|
|
305
|
+
continue # Too far apart in precedence
|
|
306
|
+
|
|
307
|
+
# Check for overlapping entities
|
|
308
|
+
entities_2 = set(re.findall(r'app\.[a-z0-9_.]+', rc2.content.lower()))
|
|
309
|
+
entities_2.update(re.findall(r'TC Setup[^>\n]*(?:>[^>\n]*)*', rc2.content, re.I))
|
|
310
|
+
|
|
311
|
+
overlap = entities_1 & entities_2
|
|
312
|
+
if overlap:
|
|
313
|
+
conflicts.append({
|
|
314
|
+
"entity": list(overlap)[0] if overlap else "unknown",
|
|
315
|
+
"source_1": rc1.source_type.name,
|
|
316
|
+
"source_2": rc2.source_type.name,
|
|
317
|
+
"rank_gap": rank_gap,
|
|
318
|
+
"chunk_1_id": getattr(rc1.chunk, 'id', None),
|
|
319
|
+
"chunk_2_id": getattr(rc2.chunk, 'id', None),
|
|
320
|
+
"recommendation": f"Prefer {rc1.source_type.name} over {rc2.source_type.name}",
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
return conflicts
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def format_source_attribution(attribution: dict) -> str:
|
|
327
|
+
"""
|
|
328
|
+
Format source attribution as human-readable text.
|
|
329
|
+
|
|
330
|
+
Example:
|
|
331
|
+
Sources (10 chunks):
|
|
332
|
+
- Gold TC: 4 chunks
|
|
333
|
+
- IGEL Docs: 3 chunks
|
|
334
|
+
- Confluence: 2 chunks
|
|
335
|
+
- Jira TC: 1 chunk
|
|
336
|
+
"""
|
|
337
|
+
lines = [f"Sources ({attribution['total_chunks']} chunks):"]
|
|
338
|
+
|
|
339
|
+
# Sort by precedence
|
|
340
|
+
breakdown = attribution['source_breakdown']
|
|
341
|
+
sorted_sources = sorted(
|
|
342
|
+
breakdown.items(),
|
|
343
|
+
key=lambda x: SOURCE_TYPES[x[0]].rank
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
for source_name, count in sorted_sources:
|
|
347
|
+
source_desc = SOURCE_TYPES[source_name].description
|
|
348
|
+
lines.append(f" - {source_desc}: {count} chunk{'s' if count > 1 else ''}")
|
|
349
|
+
|
|
350
|
+
if attribution.get('highest_precedence'):
|
|
351
|
+
highest = SOURCE_TYPES[attribution['highest_precedence']].description
|
|
352
|
+
lines.append(f"\nPrimary source: {highest}")
|
|
353
|
+
|
|
354
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Request context and API security helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import uuid
|
|
7
|
+
from contextvars import ContextVar
|
|
8
|
+
|
|
9
|
+
request_id_var: ContextVar[str] = ContextVar("request_id", default="")
|
|
10
|
+
api_key_scope_var: ContextVar[str] = ContextVar("api_key_scope", default="read")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def new_request_id() -> str:
|
|
14
|
+
rid = str(uuid.uuid4())
|
|
15
|
+
request_id_var.set(rid)
|
|
16
|
+
return rid
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_request_id() -> str:
|
|
20
|
+
return request_id_var.get() or ""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def hash_api_key(key: str | None) -> str:
|
|
24
|
+
if not key:
|
|
25
|
+
return "anonymous"
|
|
26
|
+
return hashlib.sha256(key.encode()).hexdigest()[:16]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def parse_api_keys(raw: str) -> dict[str, str]:
|
|
30
|
+
"""
|
|
31
|
+
Parse API_KEYS env: key:scope,key2:scope2
|
|
32
|
+
scope in read|write|admin
|
|
33
|
+
"""
|
|
34
|
+
out: dict[str, str] = {}
|
|
35
|
+
for part in (raw or "").split(","):
|
|
36
|
+
part = part.strip()
|
|
37
|
+
if not part:
|
|
38
|
+
continue
|
|
39
|
+
if ":" in part:
|
|
40
|
+
k, scope = part.split(":", 1)
|
|
41
|
+
out[k.strip()] = scope.strip().lower()
|
|
42
|
+
else:
|
|
43
|
+
out[part] = "admin"
|
|
44
|
+
return out
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def resolve_scope(api_key: str | None, keys: dict[str, str], legacy_key: str) -> str:
|
|
48
|
+
if not api_key:
|
|
49
|
+
return "anonymous"
|
|
50
|
+
if api_key in keys:
|
|
51
|
+
return keys[api_key]
|
|
52
|
+
if legacy_key and api_key == legacy_key:
|
|
53
|
+
return "admin"
|
|
54
|
+
return "invalid"
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
import psycopg2
|
|
7
|
+
import psycopg2.pool
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
|
|
10
|
+
from knowledge_base.config import cfg
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
_pool: psycopg2.pool.ThreadedConnectionPool | None = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _pool_kwargs() -> dict:
|
|
18
|
+
sslmode = os.getenv("DB_SSLMODE", "prefer")
|
|
19
|
+
return {
|
|
20
|
+
"host": cfg.DB_HOST,
|
|
21
|
+
"port": cfg.DB_PORT,
|
|
22
|
+
"dbname": cfg.DB_NAME,
|
|
23
|
+
"user": cfg.DB_USER,
|
|
24
|
+
"password": cfg.DB_PASSWORD,
|
|
25
|
+
"connect_timeout": 10,
|
|
26
|
+
"keepalives": 1,
|
|
27
|
+
"keepalives_idle": 30,
|
|
28
|
+
"keepalives_interval": 10,
|
|
29
|
+
"keepalives_count": 5,
|
|
30
|
+
"sslmode": sslmode,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def init_pool(minconn: int = 2, maxconn: int = 10) -> None:
|
|
35
|
+
global _pool
|
|
36
|
+
if _pool is not None:
|
|
37
|
+
return
|
|
38
|
+
_pool = psycopg2.pool.ThreadedConnectionPool(
|
|
39
|
+
minconn,
|
|
40
|
+
maxconn,
|
|
41
|
+
**_pool_kwargs(),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _ping(conn) -> None:
|
|
46
|
+
with conn.cursor() as cur:
|
|
47
|
+
cur.execute("SELECT 1")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@contextmanager
|
|
51
|
+
def get_conn(*, autocommit: bool = True):
|
|
52
|
+
"""Get a pooled connection. Commits on success when autocommit=True."""
|
|
53
|
+
if _pool is None:
|
|
54
|
+
init_pool()
|
|
55
|
+
conn = _pool.getconn()
|
|
56
|
+
try:
|
|
57
|
+
try:
|
|
58
|
+
_ping(conn)
|
|
59
|
+
except Exception:
|
|
60
|
+
conn.close()
|
|
61
|
+
conn = _pool.getconn()
|
|
62
|
+
_ping(conn)
|
|
63
|
+
yield conn
|
|
64
|
+
if autocommit:
|
|
65
|
+
conn.commit()
|
|
66
|
+
except Exception:
|
|
67
|
+
try:
|
|
68
|
+
conn.rollback()
|
|
69
|
+
except Exception as rollback_exc:
|
|
70
|
+
logger.debug("Rollback failed while handling DB error: %s", rollback_exc)
|
|
71
|
+
raise
|
|
72
|
+
finally:
|
|
73
|
+
_pool.putconn(conn)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@contextmanager
|
|
77
|
+
def get_transaction():
|
|
78
|
+
"""Yield a connection inside a single transaction (commit on success)."""
|
|
79
|
+
with get_conn(autocommit=False) as conn:
|
|
80
|
+
yield conn
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_connection() -> bool:
|
|
84
|
+
try:
|
|
85
|
+
with get_conn() as conn:
|
|
86
|
+
with conn.cursor() as cur:
|
|
87
|
+
cur.execute("SELECT version();")
|
|
88
|
+
version = cur.fetchone()[0]
|
|
89
|
+
print(f" Connected: {version[:60]}")
|
|
90
|
+
return True
|
|
91
|
+
except Exception as e:
|
|
92
|
+
print(f" DB connection failed: {e}")
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def close_pool() -> None:
|
|
97
|
+
global _pool
|
|
98
|
+
if _pool:
|
|
99
|
+
_pool.closeall()
|
|
100
|
+
_pool = None
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Connector incremental sync state persistence."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
|
|
7
|
+
from knowledge_base.db.client import get_conn
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_last_sync(connector_name: str) -> datetime | None:
|
|
11
|
+
with get_conn() as conn:
|
|
12
|
+
with conn.cursor() as cur:
|
|
13
|
+
cur.execute(
|
|
14
|
+
"SELECT last_sync_timestamp FROM connector_sync_state WHERE connector_name=%s",
|
|
15
|
+
(connector_name,),
|
|
16
|
+
)
|
|
17
|
+
row = cur.fetchone()
|
|
18
|
+
return row[0] if row and row[0] else None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def update_sync_state(
|
|
22
|
+
connector_name: str,
|
|
23
|
+
*,
|
|
24
|
+
status: str = "synced",
|
|
25
|
+
cursor: str | None = None,
|
|
26
|
+
advance_timestamp: bool = True,
|
|
27
|
+
) -> None:
|
|
28
|
+
now = datetime.now(timezone.utc)
|
|
29
|
+
last_sync_timestamp = now if advance_timestamp else None
|
|
30
|
+
with get_conn() as conn:
|
|
31
|
+
with conn.cursor() as cur:
|
|
32
|
+
cur.execute(
|
|
33
|
+
"""
|
|
34
|
+
INSERT INTO connector_sync_state (connector_name, last_sync_timestamp, cursor, status, updated_at)
|
|
35
|
+
VALUES (%s, %s, %s, %s, %s)
|
|
36
|
+
ON CONFLICT (connector_name) DO UPDATE SET
|
|
37
|
+
last_sync_timestamp = COALESCE(EXCLUDED.last_sync_timestamp, connector_sync_state.last_sync_timestamp),
|
|
38
|
+
cursor = EXCLUDED.cursor,
|
|
39
|
+
status = EXCLUDED.status,
|
|
40
|
+
updated_at = EXCLUDED.updated_at
|
|
41
|
+
""",
|
|
42
|
+
(connector_name, last_sync_timestamp, cursor, status, now),
|
|
43
|
+
)
|