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,127 @@
|
|
|
1
|
+
"""Graphiti temporal graph client — Phase 6."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from knowledge_base.config import cfg
|
|
9
|
+
from knowledge_base.db.client import get_conn
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
_client: "GraphitiClient | None" = None
|
|
14
|
+
|
|
15
|
+
_TEMPORAL_TABLE_SQL = """
|
|
16
|
+
CREATE TABLE IF NOT EXISTS temporal_feature_changes (
|
|
17
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
18
|
+
feature_name TEXT NOT NULL,
|
|
19
|
+
version TEXT NOT NULL,
|
|
20
|
+
change_type TEXT NOT NULL DEFAULT 'enhanced',
|
|
21
|
+
description TEXT,
|
|
22
|
+
affected_keys JSONB DEFAULT '[]'::jsonb,
|
|
23
|
+
affected_paths JSONB DEFAULT '[]'::jsonb,
|
|
24
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
25
|
+
UNIQUE(feature_name, version, change_type)
|
|
26
|
+
);
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_temporal_feature ON temporal_feature_changes(feature_name);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_temporal_version ON temporal_feature_changes(version);
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class GraphitiClient:
|
|
33
|
+
"""Temporal graph client — Graphiti OSS when available, else PostgreSQL store."""
|
|
34
|
+
|
|
35
|
+
def __init__(self) -> None:
|
|
36
|
+
self._graphiti = None
|
|
37
|
+
self._use_graphiti = False
|
|
38
|
+
self._ensure_table()
|
|
39
|
+
if cfg.GRAPHITI_ENABLED:
|
|
40
|
+
try:
|
|
41
|
+
import graphiti_core # noqa: F401
|
|
42
|
+
self._use_graphiti = True
|
|
43
|
+
logger.info("Graphiti: graphiti-core detected")
|
|
44
|
+
except ImportError:
|
|
45
|
+
logger.info("Graphiti: using PostgreSQL temporal fallback")
|
|
46
|
+
|
|
47
|
+
def _ensure_table(self) -> None:
|
|
48
|
+
with get_conn() as conn:
|
|
49
|
+
with conn.cursor() as cur:
|
|
50
|
+
cur.execute(_TEMPORAL_TABLE_SQL)
|
|
51
|
+
|
|
52
|
+
def health_check(self) -> dict[str, Any]:
|
|
53
|
+
return {
|
|
54
|
+
"status": "healthy",
|
|
55
|
+
"backend": "graphiti" if self._use_graphiti else "postgresql_fallback",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
def record_change(
|
|
59
|
+
self,
|
|
60
|
+
feature_name: str,
|
|
61
|
+
version: str,
|
|
62
|
+
change_type: str,
|
|
63
|
+
description: str,
|
|
64
|
+
affected_keys: list[str] | None = None,
|
|
65
|
+
affected_paths: list[str] | None = None,
|
|
66
|
+
) -> None:
|
|
67
|
+
sql = """
|
|
68
|
+
INSERT INTO temporal_feature_changes
|
|
69
|
+
(feature_name, version, change_type, description, affected_keys, affected_paths)
|
|
70
|
+
VALUES (%s, %s, %s, %s, %s::jsonb, %s::jsonb)
|
|
71
|
+
ON CONFLICT (feature_name, version, change_type) DO UPDATE SET
|
|
72
|
+
description = EXCLUDED.description,
|
|
73
|
+
affected_keys = EXCLUDED.affected_keys,
|
|
74
|
+
affected_paths = EXCLUDED.affected_paths
|
|
75
|
+
"""
|
|
76
|
+
with get_conn() as conn:
|
|
77
|
+
with conn.cursor() as cur:
|
|
78
|
+
cur.execute(sql, (
|
|
79
|
+
feature_name, version, change_type, description,
|
|
80
|
+
json.dumps(affected_keys or []),
|
|
81
|
+
json.dumps(affected_paths or []),
|
|
82
|
+
))
|
|
83
|
+
|
|
84
|
+
def query_feature_changes(self, feature_name: str, version: str) -> dict[str, Any]:
|
|
85
|
+
sql = """
|
|
86
|
+
SELECT version, change_type, description, affected_keys, affected_paths
|
|
87
|
+
FROM temporal_feature_changes
|
|
88
|
+
WHERE feature_name ILIKE %s
|
|
89
|
+
AND (version = %s OR %s = 'unspecified')
|
|
90
|
+
ORDER BY version
|
|
91
|
+
"""
|
|
92
|
+
with get_conn() as conn:
|
|
93
|
+
with conn.cursor() as cur:
|
|
94
|
+
cur.execute(sql, (f"%{feature_name}%", version, version))
|
|
95
|
+
rows = cur.fetchall()
|
|
96
|
+
|
|
97
|
+
if not rows:
|
|
98
|
+
return {"status": "no_changes_found", "version_queried": version, "feature_changes": []}
|
|
99
|
+
|
|
100
|
+
changes = []
|
|
101
|
+
for row in rows:
|
|
102
|
+
changes.append({
|
|
103
|
+
"version": row[0],
|
|
104
|
+
"change_type": row[1],
|
|
105
|
+
"description": row[2],
|
|
106
|
+
"affected_registry_keys": row[3] if isinstance(row[3], list) else json.loads(row[3] or "[]"),
|
|
107
|
+
"affected_ui_paths": row[4] if isinstance(row[4], list) else json.loads(row[4] or "[]"),
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
"status": "complete",
|
|
112
|
+
"version_queried": version,
|
|
113
|
+
"feature_changes": changes,
|
|
114
|
+
"deprecated_in_version": [],
|
|
115
|
+
"added_in_version": [],
|
|
116
|
+
"renamed_in_version": [],
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
def query_latest(self, feature_name: str) -> dict[str, Any]:
|
|
120
|
+
return self.query_feature_changes(feature_name, "12.10")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_graphiti_client() -> GraphitiClient:
|
|
124
|
+
global _client
|
|
125
|
+
if _client is None:
|
|
126
|
+
_client = GraphitiClient()
|
|
127
|
+
return _client
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Neo4j multi-hop KG traversal — Phase 3."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from knowledge_base.config import cfg
|
|
9
|
+
from knowledge_base.graph_db.neo4j_client import get_neo4j_client
|
|
10
|
+
from knowledge_base.retrieval.knowledge_graph import KGContext
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def retrieve_kg_context_neo4j(
|
|
16
|
+
query: str,
|
|
17
|
+
max_hops: int = 2,
|
|
18
|
+
max_nodes: int = 48,
|
|
19
|
+
feature_area: str | None = None,
|
|
20
|
+
seed_parent_chunk_ids: list[str] | None = None,
|
|
21
|
+
) -> KGContext:
|
|
22
|
+
"""Traverse Neo4j graph for registry keys, UI paths, and related entities."""
|
|
23
|
+
client = get_neo4j_client()
|
|
24
|
+
if not client.available:
|
|
25
|
+
logger.warning("Neo4j unavailable — returning empty KG context")
|
|
26
|
+
return KGContext(text_block="", node_ids=[], node_count=0, edge_count=0)
|
|
27
|
+
|
|
28
|
+
terms = _search_terms(query)
|
|
29
|
+
feature = feature_area or _infer_feature(query)
|
|
30
|
+
hops = max(0, min(int(max_hops), 3))
|
|
31
|
+
|
|
32
|
+
cypher = """
|
|
33
|
+
MATCH (f:Feature)
|
|
34
|
+
WHERE toLower(f.name) CONTAINS toLower($feature)
|
|
35
|
+
OR ANY(t IN $terms WHERE toLower(f.name) CONTAINS toLower(t))
|
|
36
|
+
OPTIONAL MATCH (f)-[:USES]->(rk:RegistryKey)
|
|
37
|
+
OPTIONAL MATCH (f)-[:CONFIGURED_VIA]->(up:UIPath)
|
|
38
|
+
RETURN f.name AS feature, collect(DISTINCT rk.key) AS keys,
|
|
39
|
+
collect(DISTINCT up.path) AS paths
|
|
40
|
+
LIMIT 1
|
|
41
|
+
"""
|
|
42
|
+
rows = _run_query(client, cypher, {"feature": feature, "terms": terms[:8]})
|
|
43
|
+
|
|
44
|
+
keys: list[str] = []
|
|
45
|
+
paths: list[str] = []
|
|
46
|
+
features: list[str] = []
|
|
47
|
+
|
|
48
|
+
if rows:
|
|
49
|
+
row = rows[0]
|
|
50
|
+
features.append(row.get("feature") or feature)
|
|
51
|
+
keys = [k for k in (row.get("keys") or []) if k]
|
|
52
|
+
paths = [p for p in (row.get("paths") or []) if p]
|
|
53
|
+
|
|
54
|
+
if hops > 0 and features:
|
|
55
|
+
ext_cypher = """
|
|
56
|
+
MATCH (f:Feature {name: $feature})-[*1..$hops]-(related)
|
|
57
|
+
WHERE related:RegistryKey OR related:UIPath OR related:Feature
|
|
58
|
+
RETURN labels(related)[0] AS lbl,
|
|
59
|
+
coalesce(related.key, related.path, related.name) AS val
|
|
60
|
+
LIMIT $limit
|
|
61
|
+
"""
|
|
62
|
+
ext_rows = _run_query(
|
|
63
|
+
client,
|
|
64
|
+
ext_cypher.replace("$hops", str(hops)),
|
|
65
|
+
{"feature": features[0], "limit": max_nodes},
|
|
66
|
+
)
|
|
67
|
+
for er in ext_rows:
|
|
68
|
+
lbl = er.get("lbl", "")
|
|
69
|
+
val = er.get("val")
|
|
70
|
+
if not val:
|
|
71
|
+
continue
|
|
72
|
+
if lbl == "RegistryKey" and val not in keys:
|
|
73
|
+
keys.append(val)
|
|
74
|
+
elif lbl == "UIPath" and val not in paths:
|
|
75
|
+
paths.append(val)
|
|
76
|
+
|
|
77
|
+
text_block = _format_block(keys, paths, features)
|
|
78
|
+
node_count = len(keys) + len(paths) + len(features)
|
|
79
|
+
|
|
80
|
+
return KGContext(
|
|
81
|
+
text_block=text_block,
|
|
82
|
+
node_ids=[f"neo4j:{v}" for v in (keys + paths + features)[:max_nodes]],
|
|
83
|
+
node_count=node_count,
|
|
84
|
+
edge_count=0,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def traverse_failure_path(test_id: str) -> dict[str, Any]:
|
|
89
|
+
"""RCA traversal: Testcase → Feature → RegistryKey → Defect."""
|
|
90
|
+
client = get_neo4j_client()
|
|
91
|
+
if not client.available:
|
|
92
|
+
return {"status": "insufficient_graph_data", "chain": [], "missing": ["neo4j"]}
|
|
93
|
+
|
|
94
|
+
cypher = """
|
|
95
|
+
MATCH path = (tc:Testcase)-[*1..3]-(n)
|
|
96
|
+
WHERE tc.id CONTAINS $test_id OR tc.name CONTAINS $test_id
|
|
97
|
+
RETURN [node IN nodes(path) | coalesce(node.name, node.key, node.id)] AS chain
|
|
98
|
+
LIMIT 5
|
|
99
|
+
"""
|
|
100
|
+
rows = _run_query(client, cypher, {"test_id": test_id})
|
|
101
|
+
chains = [r.get("chain", []) for r in rows if r.get("chain")]
|
|
102
|
+
if not chains:
|
|
103
|
+
return {"status": "insufficient_graph_data", "chain": [], "missing": ["testcase_node"]}
|
|
104
|
+
return {"status": "complete", "root_cause_chain": chains[0], "all_paths": chains}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def traverse_change_impact(changed_files: list[str]) -> list[dict]:
|
|
108
|
+
"""Regression: changed files → features → testcases."""
|
|
109
|
+
client = get_neo4j_client()
|
|
110
|
+
if not client.available:
|
|
111
|
+
return []
|
|
112
|
+
|
|
113
|
+
results = []
|
|
114
|
+
for fpath in changed_files[:50]:
|
|
115
|
+
feature = _file_to_feature(fpath)
|
|
116
|
+
cypher = """
|
|
117
|
+
MATCH (feat:Feature)
|
|
118
|
+
WHERE toLower(feat.name) CONTAINS toLower($feature)
|
|
119
|
+
OPTIONAL MATCH (feat)-[:HAS_TESTCASE]->(tc:Testcase)
|
|
120
|
+
RETURN feat.name AS feature, collect(DISTINCT tc.id) AS test_ids
|
|
121
|
+
LIMIT 1
|
|
122
|
+
"""
|
|
123
|
+
rows = _run_query(client, cypher, {"feature": feature})
|
|
124
|
+
for row in rows:
|
|
125
|
+
for tid in row.get("test_ids") or []:
|
|
126
|
+
if tid:
|
|
127
|
+
results.append({
|
|
128
|
+
"test_id": tid,
|
|
129
|
+
"feature": row.get("feature"),
|
|
130
|
+
"changed_file": fpath,
|
|
131
|
+
"risk_score": 0.7,
|
|
132
|
+
})
|
|
133
|
+
return results
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _run_query(client, cypher: str, params: dict) -> list[dict]:
|
|
137
|
+
if not client.available:
|
|
138
|
+
return []
|
|
139
|
+
try:
|
|
140
|
+
with client._driver.session() as session:
|
|
141
|
+
result = session.run(cypher, params)
|
|
142
|
+
return [dict(r) for r in result]
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.warning("Neo4j query failed: %s", e)
|
|
145
|
+
return []
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _search_terms(query: str) -> list[str]:
|
|
149
|
+
keys = re.findall(r"app\.[a-z0-9_.]+", query, flags=re.I)
|
|
150
|
+
tokens = [t for t in re.split(r"[^\w\-./]+", query) if len(t) >= 3]
|
|
151
|
+
out: list[str] = []
|
|
152
|
+
for x in keys + tokens[:12]:
|
|
153
|
+
if x.lower() not in {y.lower() for y in out}:
|
|
154
|
+
out.append(x)
|
|
155
|
+
return out
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _infer_feature(query: str) -> str:
|
|
159
|
+
q = query.lower()
|
|
160
|
+
if "entra" in q or "sso" in q:
|
|
161
|
+
return "Entra ID SSO"
|
|
162
|
+
if "wifi" in q or "802.1" in q:
|
|
163
|
+
return "Network WiFi 802.1x"
|
|
164
|
+
if "icg" in q or "certificate" in q:
|
|
165
|
+
return "ICG Certificate Management"
|
|
166
|
+
if "chromium" in q:
|
|
167
|
+
return "Chromium Browser"
|
|
168
|
+
return query[:80]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _file_to_feature(path: str) -> str:
|
|
172
|
+
name = path.split("/")[-1].replace("_", " ").replace(".py", "")
|
|
173
|
+
return name[:80]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _format_block(keys: list[str], paths: list[str], features: list[str]) -> str:
|
|
177
|
+
if not keys and not paths and not features:
|
|
178
|
+
return ""
|
|
179
|
+
lines = ["—— Knowledge graph entities (Neo4j) ——"]
|
|
180
|
+
if features:
|
|
181
|
+
lines.append("\n[Features & sections]")
|
|
182
|
+
for f in features[:10]:
|
|
183
|
+
lines.append(f" • {f}")
|
|
184
|
+
if paths:
|
|
185
|
+
lines.append("\n[TC Setup / UI navigation paths]")
|
|
186
|
+
for p in paths[:20]:
|
|
187
|
+
lines.append(f" • {p}")
|
|
188
|
+
if keys:
|
|
189
|
+
lines.append("\n[Registry keys]")
|
|
190
|
+
for k in keys[:30]:
|
|
191
|
+
lines.append(f" • {k}")
|
|
192
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Migrate PostgreSQL knowledge graph to Neo4j (Phase 3).
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python -m knowledge_base.graph_db.migrate_kg [--dry-run]
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
from knowledge_base.db.client import get_conn, init_pool
|
|
13
|
+
from knowledge_base.graph_db.neo4j_client import get_neo4j_client, should_write_neo4j
|
|
14
|
+
from knowledge_base.graph_db.schema import apply_schema
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
_LABEL_MAP = {
|
|
19
|
+
"feature": "Feature",
|
|
20
|
+
"registry_key": "RegistryKey",
|
|
21
|
+
"ui_path": "UIPath",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def migrate(*, dry_run: bool = False) -> dict:
|
|
26
|
+
"""Read PG KG tables and write to Neo4j."""
|
|
27
|
+
if not should_write_neo4j():
|
|
28
|
+
logger.warning("Neo4j migration skipped: set KG_SHADOW_MODE=true or KG_BACKEND=neo4j")
|
|
29
|
+
return {"nodes": 0, "edges": 0, "skipped": True}
|
|
30
|
+
|
|
31
|
+
client = get_neo4j_client()
|
|
32
|
+
if not client.available:
|
|
33
|
+
logger.error("Neo4j unavailable — cannot migrate")
|
|
34
|
+
return {"nodes": 0, "edges": 0, "error": "neo4j_unavailable"}
|
|
35
|
+
|
|
36
|
+
if dry_run:
|
|
37
|
+
logger.info("Dry run — counting PG nodes/edges only")
|
|
38
|
+
else:
|
|
39
|
+
apply_schema(client)
|
|
40
|
+
|
|
41
|
+
init_pool()
|
|
42
|
+
node_count = 0
|
|
43
|
+
edge_count = 0
|
|
44
|
+
|
|
45
|
+
with get_conn() as conn:
|
|
46
|
+
with conn.cursor() as cur:
|
|
47
|
+
cur.execute(
|
|
48
|
+
"SELECT node_type, node_value, metadata FROM knowledge_graph_nodes"
|
|
49
|
+
)
|
|
50
|
+
nodes = cur.fetchall()
|
|
51
|
+
|
|
52
|
+
for node_type, node_value, metadata in nodes:
|
|
53
|
+
label = _LABEL_MAP.get(node_type, "Feature")
|
|
54
|
+
key_field = "name" if label == "Feature" else ("key" if label == "RegistryKey" else "path")
|
|
55
|
+
if not dry_run:
|
|
56
|
+
client.upsert_entity(label, key_field, node_value, metadata or {})
|
|
57
|
+
node_count += 1
|
|
58
|
+
|
|
59
|
+
cur.execute(
|
|
60
|
+
"""
|
|
61
|
+
SELECT e.relationship, n1.node_type, n1.node_value, n2.node_type, n2.node_value
|
|
62
|
+
FROM knowledge_graph_edges e
|
|
63
|
+
JOIN knowledge_graph_nodes n1 ON e.from_node_id = n1.id
|
|
64
|
+
JOIN knowledge_graph_nodes n2 ON e.to_node_id = n2.id
|
|
65
|
+
"""
|
|
66
|
+
)
|
|
67
|
+
edges = cur.fetchall()
|
|
68
|
+
|
|
69
|
+
for rel, t1, v1, t2, v2 in edges:
|
|
70
|
+
l1 = _LABEL_MAP.get(t1, "Feature")
|
|
71
|
+
l2 = _LABEL_MAP.get(t2, "RegistryKey")
|
|
72
|
+
k1 = "name" if l1 == "Feature" else ("key" if l1 == "RegistryKey" else "path")
|
|
73
|
+
k2 = "name" if l2 == "Feature" else ("key" if l2 == "RegistryKey" else "path")
|
|
74
|
+
rel_type = rel.upper().replace(" ", "_")
|
|
75
|
+
if not dry_run:
|
|
76
|
+
client.upsert_relationship(l1, k1, v1, rel_type, l2, k2, v2)
|
|
77
|
+
edge_count += 1
|
|
78
|
+
|
|
79
|
+
logger.info("Migration complete: %d nodes, %d edges (dry_run=%s)", node_count, edge_count, dry_run)
|
|
80
|
+
return {"nodes": node_count, "edges": edge_count, "dry_run": dry_run}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def main() -> None:
|
|
84
|
+
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
|
|
85
|
+
parser = argparse.ArgumentParser(description="Migrate PostgreSQL KG to Neo4j")
|
|
86
|
+
parser.add_argument("--dry-run", action="store_true")
|
|
87
|
+
args = parser.parse_args()
|
|
88
|
+
result = migrate(dry_run=args.dry_run)
|
|
89
|
+
print(result)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if __name__ == "__main__":
|
|
93
|
+
main()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Neo4j client abstraction with graceful degradation when unavailable."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from knowledge_base.config import cfg
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
_client: "Neo4jClient | None" = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Neo4jClient:
|
|
15
|
+
"""Thin wrapper around the Neo4j driver. No-ops when driver is unavailable."""
|
|
16
|
+
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
self._driver = None
|
|
19
|
+
self._available = False
|
|
20
|
+
self._connect()
|
|
21
|
+
|
|
22
|
+
def _connect(self) -> None:
|
|
23
|
+
if not cfg.NEO4J_PASSWORD:
|
|
24
|
+
logger.info("Neo4j: NEO4J_PASSWORD not set — shadow mode disabled")
|
|
25
|
+
return
|
|
26
|
+
try:
|
|
27
|
+
from neo4j import GraphDatabase
|
|
28
|
+
|
|
29
|
+
self._driver = GraphDatabase.driver(
|
|
30
|
+
cfg.NEO4J_URI,
|
|
31
|
+
auth=(cfg.NEO4J_USER, cfg.NEO4J_PASSWORD),
|
|
32
|
+
)
|
|
33
|
+
self._driver.verify_connectivity()
|
|
34
|
+
self._available = True
|
|
35
|
+
logger.info("Neo4j: connected to %s", cfg.NEO4J_URI)
|
|
36
|
+
except ImportError:
|
|
37
|
+
logger.warning("Neo4j: neo4j driver not installed — pip install neo4j")
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logger.warning("Neo4j: connection failed (%s) — shadow writes skipped", e)
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def available(self) -> bool:
|
|
43
|
+
return self._available and self._driver is not None
|
|
44
|
+
|
|
45
|
+
def health_check(self) -> dict[str, Any]:
|
|
46
|
+
if not self.available:
|
|
47
|
+
return {"status": "unavailable", "uri": cfg.NEO4J_URI}
|
|
48
|
+
try:
|
|
49
|
+
with self._driver.session() as session:
|
|
50
|
+
result = session.run("RETURN 1 AS ok")
|
|
51
|
+
ok = result.single()["ok"]
|
|
52
|
+
return {"status": "healthy" if ok == 1 else "degraded", "uri": cfg.NEO4J_URI}
|
|
53
|
+
except Exception as e:
|
|
54
|
+
return {"status": "error", "uri": cfg.NEO4J_URI, "error": str(e)}
|
|
55
|
+
|
|
56
|
+
def run_write(self, cypher: str, params: dict | None = None) -> None:
|
|
57
|
+
if not self.available:
|
|
58
|
+
return
|
|
59
|
+
with self._driver.session() as session:
|
|
60
|
+
session.run(cypher, params or {})
|
|
61
|
+
|
|
62
|
+
def upsert_entity(
|
|
63
|
+
self,
|
|
64
|
+
label: str,
|
|
65
|
+
key_field: str,
|
|
66
|
+
key_value: str,
|
|
67
|
+
properties: dict | None = None,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""MERGE a node by key field."""
|
|
70
|
+
props = properties or {}
|
|
71
|
+
cypher = f"""
|
|
72
|
+
MERGE (n:{label} {{{key_field}: $key_value}})
|
|
73
|
+
SET n += $props
|
|
74
|
+
"""
|
|
75
|
+
self.run_write(cypher, {"key_value": key_value, "props": props})
|
|
76
|
+
|
|
77
|
+
def upsert_relationship(
|
|
78
|
+
self,
|
|
79
|
+
from_label: str,
|
|
80
|
+
from_key: str,
|
|
81
|
+
from_value: str,
|
|
82
|
+
rel_type: str,
|
|
83
|
+
to_label: str,
|
|
84
|
+
to_key: str,
|
|
85
|
+
to_value: str,
|
|
86
|
+
rel_props: dict | None = None,
|
|
87
|
+
) -> None:
|
|
88
|
+
cypher = f"""
|
|
89
|
+
MERGE (a:{from_label} {{{from_key}: $from_value}})
|
|
90
|
+
MERGE (b:{to_label} {{{to_key}: $to_value}})
|
|
91
|
+
MERGE (a)-[r:{rel_type}]->(b)
|
|
92
|
+
SET r += $rel_props
|
|
93
|
+
"""
|
|
94
|
+
self.run_write(cypher, {
|
|
95
|
+
"from_value": from_value,
|
|
96
|
+
"to_value": to_value,
|
|
97
|
+
"rel_props": rel_props or {},
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
def close(self) -> None:
|
|
101
|
+
if self._driver:
|
|
102
|
+
self._driver.close()
|
|
103
|
+
self._driver = None
|
|
104
|
+
self._available = False
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def get_neo4j_client() -> Neo4jClient:
|
|
108
|
+
global _client
|
|
109
|
+
if _client is None:
|
|
110
|
+
_client = Neo4jClient()
|
|
111
|
+
return _client
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def should_write_neo4j() -> bool:
|
|
115
|
+
"""True when Neo4j shadow or primary backend is active."""
|
|
116
|
+
return cfg.KG_BACKEND == "neo4j" or cfg.KG_SHADOW_MODE
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Cypher schema definitions for IGEL EKG Neo4j graph (Phase 3)."""
|
|
2
|
+
|
|
3
|
+
CONSTRAINTS = [
|
|
4
|
+
"CREATE CONSTRAINT feature_name IF NOT EXISTS FOR (f:Feature) REQUIRE f.name IS UNIQUE",
|
|
5
|
+
"CREATE CONSTRAINT registry_key_name IF NOT EXISTS FOR (r:RegistryKey) REQUIRE r.key IS UNIQUE",
|
|
6
|
+
"CREATE CONSTRAINT ui_path_path IF NOT EXISTS FOR (u:UIPath) REQUIRE u.path IS UNIQUE",
|
|
7
|
+
"CREATE CONSTRAINT testcase_id IF NOT EXISTS FOR (t:Testcase) REQUIRE t.id IS UNIQUE",
|
|
8
|
+
"CREATE CONSTRAINT version_name IF NOT EXISTS FOR (v:Version) REQUIRE v.version IS UNIQUE",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
INDEXES = [
|
|
12
|
+
"CREATE INDEX feature_name_idx IF NOT EXISTS FOR (f:Feature) ON (f.name)",
|
|
13
|
+
"CREATE INDEX registry_key_idx IF NOT EXISTS FOR (r:RegistryKey) ON (r.key)",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def apply_schema(client) -> None:
|
|
18
|
+
"""Apply constraints and indexes to Neo4j."""
|
|
19
|
+
if not client.available:
|
|
20
|
+
return
|
|
21
|
+
for stmt in CONSTRAINTS + INDEXES:
|
|
22
|
+
client.run_write(stmt)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Version-aware temporal ingestion — Phase 6."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from knowledge_base.graph_db.graphiti_client import get_graphiti_client
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
_VERSION_RE = re.compile(r"IGEL\s*OS\s*(12\.\d+(?:\.\d+)?)", re.I)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def extract_version_from_metadata(metadata: dict) -> str | None:
|
|
15
|
+
for key in ("version", "igel_version", "product_version"):
|
|
16
|
+
val = metadata.get(key)
|
|
17
|
+
if val and "12." in str(val):
|
|
18
|
+
return str(val).split()[0] if " " in str(val) else str(val)
|
|
19
|
+
title = metadata.get("title") or metadata.get("file_name") or ""
|
|
20
|
+
m = _VERSION_RE.search(str(title))
|
|
21
|
+
return m.group(1) if m else None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def ingest_document_temporal(
|
|
25
|
+
document_title: str,
|
|
26
|
+
content: str,
|
|
27
|
+
version: str | None = None,
|
|
28
|
+
feature_name: str | None = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Create temporal edges for version-tagged documents."""
|
|
31
|
+
client = get_graphiti_client()
|
|
32
|
+
ver = version or "12.10"
|
|
33
|
+
feature = feature_name or document_title[:80]
|
|
34
|
+
|
|
35
|
+
keys = re.findall(r"\b(app\.[a-z0-9_.]+)\b", content, flags=re.I)
|
|
36
|
+
paths = re.findall(r"UMS[^>\n]{5,100}(?:>[^>\n]+)+", content)
|
|
37
|
+
|
|
38
|
+
client.record_change(
|
|
39
|
+
feature_name=feature,
|
|
40
|
+
version=ver,
|
|
41
|
+
change_type="document_ingested",
|
|
42
|
+
description=f"Ingested: {document_title[:200]}",
|
|
43
|
+
affected_keys=keys[:20],
|
|
44
|
+
affected_paths=paths[:10],
|
|
45
|
+
)
|
|
46
|
+
logger.debug("Temporal ingest: %s @ %s", feature, ver)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def seed_default_versions() -> None:
|
|
50
|
+
"""Seed known IGEL version evolution data."""
|
|
51
|
+
client = get_graphiti_client()
|
|
52
|
+
seeds = [
|
|
53
|
+
("Entra ID SSO", "12.9", "enhanced", "Added MFA support to Entra ID SSO",
|
|
54
|
+
["security.entra.mfa_enabled"], ["UMS Console > Profiles > Identity > Entra ID > MFA"]),
|
|
55
|
+
("Entra ID SSO", "12.8", "introduced", "Initial Entra ID SSO support", [], []),
|
|
56
|
+
("Network WiFi 802.1x", "12.10", "changed", "Revised network registry keys", [], []),
|
|
57
|
+
]
|
|
58
|
+
for feature, ver, ctype, desc, keys, paths in seeds:
|
|
59
|
+
client.record_change(feature, ver, ctype, desc, keys, paths)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Document and knowledge graph ingestion."""
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|