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