contextsynapse 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (776) hide show
  1. contextsynapse/__init__.py +107 -0
  2. contextsynapse/__main__.py +9 -0
  3. contextsynapse/a2a/__init__.py +28 -0
  4. contextsynapse/a2a/client.py +120 -0
  5. contextsynapse/a2a/discovery.py +128 -0
  6. contextsynapse/a2a/handlers.py +164 -0
  7. contextsynapse/a2a/models.py +246 -0
  8. contextsynapse/a2a/streaming.py +92 -0
  9. contextsynapse/a2a/task_manager.py +275 -0
  10. contextsynapse/adapters/__init__.py +98 -0
  11. contextsynapse/adapters/_base.py +298 -0
  12. contextsynapse/adapters/autogen/__init__.py +4 -0
  13. contextsynapse/adapters/autogen/tools.py +34 -0
  14. contextsynapse/adapters/crewai/__init__.py +4 -0
  15. contextsynapse/adapters/crewai/tools.py +63 -0
  16. contextsynapse/adapters/langchain/__init__.py +19 -0
  17. contextsynapse/adapters/langchain/message_history.py +17 -0
  18. contextsynapse/adapters/langchain/retriever.py +158 -0
  19. contextsynapse/adapters/langchain/tools.py +43 -0
  20. contextsynapse/adapters/langgraph/__init__.py +21 -0
  21. contextsynapse/adapters/langgraph/checkpoint.py +223 -0
  22. contextsynapse/adapters/langgraph/context_tools.py +341 -0
  23. contextsynapse/adapters/langgraph/message_history.py +138 -0
  24. contextsynapse/adapters/langgraph/tools.py +43 -0
  25. contextsynapse/adapters/llamaindex/__init__.py +4 -0
  26. contextsynapse/adapters/llamaindex/tools.py +64 -0
  27. contextsynapse/adapters/openai/__init__.py +24 -0
  28. contextsynapse/adapters/openai/tools.py +206 -0
  29. contextsynapse/adapters/pydantic_ai/__init__.py +4 -0
  30. contextsynapse/adapters/pydantic_ai/tools.py +36 -0
  31. contextsynapse/adapters/swarm/__init__.py +4 -0
  32. contextsynapse/adapters/swarm/tools.py +44 -0
  33. contextsynapse/agents/__init__.py +6 -0
  34. contextsynapse/agents/auto_capture.py +80 -0
  35. contextsynapse/agents/dispatcher.py +299 -0
  36. contextsynapse/agents/notifications.py +112 -0
  37. contextsynapse/aiql/__init__.py +71 -0
  38. contextsynapse/aiql/cache.py +341 -0
  39. contextsynapse/aiql/compiler/__init__.py +62 -0
  40. contextsynapse/aiql/compiler/optimizer.py +126 -0
  41. contextsynapse/aiql/compiler/planner.py +366 -0
  42. contextsynapse/aiql/compiler/statistics.py +235 -0
  43. contextsynapse/aiql/compiler/validator.py +425 -0
  44. contextsynapse/aiql/engine/__init__.py +11 -0
  45. contextsynapse/aiql/engine/chunk_storage.py +2584 -0
  46. contextsynapse/aiql/engine/executor.py +7737 -0
  47. contextsynapse/aiql/engine/extraction_integration.py +442 -0
  48. contextsynapse/aiql/engine/graph_builder.py +405 -0
  49. contextsynapse/aiql/engine/node_creation_from_normalized.py +691 -0
  50. contextsynapse/aiql/engine/performance_monitor.py +289 -0
  51. contextsynapse/aiql/engine/postprocessing.py +365 -0
  52. contextsynapse/aiql/engine/ray_stage_executors.py +918 -0
  53. contextsynapse/aiql/engine/redis_cache.py +106 -0
  54. contextsynapse/aiql/engine/streaming_json.py +366 -0
  55. contextsynapse/aiql/engine/streaming_parquet.py +568 -0
  56. contextsynapse/aiql/engine/streaming_ray_chunking.py +419 -0
  57. contextsynapse/aiql/grammar/__init__.py +14 -0
  58. contextsynapse/aiql/grammar/aiql_grammar.py +1822 -0
  59. contextsynapse/aiql/grammar/base.py +71 -0
  60. contextsynapse/aiql/parser/__init__.py +46 -0
  61. contextsynapse/aiql/parser/aiql_parser.py +11330 -0
  62. contextsynapse/aiql/parser/base_parser.py +47 -0
  63. contextsynapse/api/__init__.py +5 -0
  64. contextsynapse/api/a2a_router.py +233 -0
  65. contextsynapse/api/admin_router.py +140 -0
  66. contextsynapse/api/agent_worker_router.py +978 -0
  67. contextsynapse/api/algorithms_router.py +317 -0
  68. contextsynapse/api/api.py +4144 -0
  69. contextsynapse/api/audit.py +204 -0
  70. contextsynapse/api/auth.py +441 -0
  71. contextsynapse/api/auth_router.py +537 -0
  72. contextsynapse/api/billing.py +261 -0
  73. contextsynapse/api/billing_router.py +275 -0
  74. contextsynapse/api/boundary_router.py +452 -0
  75. contextsynapse/api/cognition_router.py +116 -0
  76. contextsynapse/api/connector_router.py +237 -0
  77. contextsynapse/api/context_router.py +1619 -0
  78. contextsynapse/api/dashboard_router.py +11367 -0
  79. contextsynapse/api/errors.py +183 -0
  80. contextsynapse/api/events.py +88 -0
  81. contextsynapse/api/experiment_bootstrap.py +321 -0
  82. contextsynapse/api/experiment_orchestrator.py +1078 -0
  83. contextsynapse/api/experiment_router.py +2089 -0
  84. contextsynapse/api/feature_gate.py +89 -0
  85. contextsynapse/api/federation_router.py +108 -0
  86. contextsynapse/api/integrations.py +567 -0
  87. contextsynapse/api/integrations_router.py +461 -0
  88. contextsynapse/api/intelligence_router.py +1028 -0
  89. contextsynapse/api/metering.py +155 -0
  90. contextsynapse/api/metrics.py +112 -0
  91. contextsynapse/api/models.py +38 -0
  92. contextsynapse/api/monitoring_router.py +174 -0
  93. contextsynapse/api/pipeline_router.py +1437 -0
  94. contextsynapse/api/playground_conn.py +116 -0
  95. contextsynapse/api/projects_router.py +131 -0
  96. contextsynapse/api/queries_router.py +282 -0
  97. contextsynapse/api/quota.py +95 -0
  98. contextsynapse/api/rate_limit.py +64 -0
  99. contextsynapse/api/search_router.py +261 -0
  100. contextsynapse/api/shield_router.py +447 -0
  101. contextsynapse/api/tenant_guard.py +103 -0
  102. contextsynapse/api/tenant_quotas.py +231 -0
  103. contextsynapse/api/tenants.py +305 -0
  104. contextsynapse/api/users.py +646 -0
  105. contextsynapse/api/v1.py +167 -0
  106. contextsynapse/api/vertical_builder_router.py +387 -0
  107. contextsynapse/benchmark/__init__.py +15 -0
  108. contextsynapse/benchmark/__main__.py +64 -0
  109. contextsynapse/benchmark/agents.py +338 -0
  110. contextsynapse/benchmark/live_ab.py +338 -0
  111. contextsynapse/benchmark/reporter.py +149 -0
  112. contextsynapse/benchmark/runner.py +264 -0
  113. contextsynapse/benchmark/savings.py +266 -0
  114. contextsynapse/benchmark/token_counter.py +212 -0
  115. contextsynapse/benchmark/workloads.py +289 -0
  116. contextsynapse/buffer/__init__.py +154 -0
  117. contextsynapse/buffer/base.py +257 -0
  118. contextsynapse/buffer/duckdb_buffer.py +246 -0
  119. contextsynapse/buffer/file_buffer.py +229 -0
  120. contextsynapse/buffer/lmdb_buffer.py +286 -0
  121. contextsynapse/buffer/local_buffer.py +154 -0
  122. contextsynapse/buffer/redis_buffer.py +211 -0
  123. contextsynapse/buffer/sqlite_buffer.py +266 -0
  124. contextsynapse/cache.py +2 -0
  125. contextsynapse/cli/__init__.py +40 -0
  126. contextsynapse/cli/cli.py +475 -0
  127. contextsynapse/cli/config_cli.py +177 -0
  128. contextsynapse/cli/scaffold.py +465 -0
  129. contextsynapse/cognition/__init__.py +31 -0
  130. contextsynapse/cognition/derivation.py +55 -0
  131. contextsynapse/cognition/events.py +135 -0
  132. contextsynapse/cognition/feedback.py +52 -0
  133. contextsynapse/cognition/hallucination.py +90 -0
  134. contextsynapse/cognition/invalidation.py +70 -0
  135. contextsynapse/cognition/read_tracker.py +50 -0
  136. contextsynapse/collaboration.py +2 -0
  137. contextsynapse/comparison.py +2 -0
  138. contextsynapse/config/__init__.py +10 -0
  139. contextsynapse/config/connectors/amfi_nav.yaml +18 -0
  140. contextsynapse/config/connectors/auto_sector.yaml +20 -0
  141. contextsynapse/config/connectors/banking_sector.yaml +20 -0
  142. contextsynapse/config/connectors/energy_sector.yaml +20 -0
  143. contextsynapse/config/connectors/fmcg_sector.yaml +20 -0
  144. contextsynapse/config/connectors/nsdl_fpi.yaml +9 -0
  145. contextsynapse/config/connectors/nse_price.yaml +61 -0
  146. contextsynapse/config/connectors/pharma_sector.yaml +20 -0
  147. contextsynapse/config/connectors/rbi_dbie.yaml +12 -0
  148. contextsynapse/config/context_presets/ai_research.json +25 -0
  149. contextsynapse/config/context_presets/chatgpt_conversations.json +19 -0
  150. contextsynapse/config/context_presets/claude_conversations.json +19 -0
  151. contextsynapse/config/context_presets/legal_compliance.json +21 -0
  152. contextsynapse/config/context_presets/meeting_notes.json +21 -0
  153. contextsynapse/config/context_presets/slack_engineering.json +21 -0
  154. contextsynapse/config/context_presets/tech_climate.json +24 -0
  155. contextsynapse/config/context_presets/toi_politics.json +25 -0
  156. contextsynapse/config/database_config.py +453 -0
  157. contextsynapse/config/deployment.py +221 -0
  158. contextsynapse/config/gpt_action_schema.json +164 -0
  159. contextsynapse/config/indicators/india_macro.yaml +213 -0
  160. contextsynapse/config/logging_config.py +88 -0
  161. contextsynapse/config/mode_config.py +223 -0
  162. contextsynapse/config/schemas/api_spec.yaml +48 -0
  163. contextsynapse/config/schemas/architecture_doc.yaml +51 -0
  164. contextsynapse/config/schemas/chat_history.yaml +90 -0
  165. contextsynapse/config/schemas/chatgpt_conversation.yaml +101 -0
  166. contextsynapse/config/schemas/claude_conversation.yaml +148 -0
  167. contextsynapse/config/schemas/contract.yaml +48 -0
  168. contextsynapse/config/schemas/conversation.yaml +167 -0
  169. contextsynapse/config/schemas/database.yaml +74 -0
  170. contextsynapse/config/schemas/decision.yaml +72 -0
  171. contextsynapse/config/schemas/ecommerce.yaml +56 -0
  172. contextsynapse/config/schemas/education.yaml +42 -0
  173. contextsynapse/config/schemas/healthcare.yaml +133 -0
  174. contextsynapse/config/schemas/hr_document.yaml +48 -0
  175. contextsynapse/config/schemas/invoice.yaml +40 -0
  176. contextsynapse/config/schemas/knowledge_base.yaml +75 -0
  177. contextsynapse/config/schemas/legal.yaml +42 -0
  178. contextsynapse/config/schemas/marketing_content.yaml +48 -0
  179. contextsynapse/config/schemas/meeting_notes.yaml +51 -0
  180. contextsynapse/config/schemas/news_article.yaml +61 -0
  181. contextsynapse/config/schemas/real_estate.yaml +49 -0
  182. contextsynapse/config/schemas/requirements_doc.yaml +48 -0
  183. contextsynapse/config/schemas/research_paper.yaml +56 -0
  184. contextsynapse/config/schemas/resume.yaml +49 -0
  185. contextsynapse/config/schemas/retail.yaml +42 -0
  186. contextsynapse/config/schemas/rule_meta.yaml +173 -0
  187. contextsynapse/config/schemas/rules.yaml +72 -0
  188. contextsynapse/config/schemas/screener_meta.yaml +236 -0
  189. contextsynapse/config/schemas/sdlc.yaml +103 -0
  190. contextsynapse/config/schemas/session_graph.yaml +172 -0
  191. contextsynapse/config/schemas/support_ticket.yaml +48 -0
  192. contextsynapse/config/schemas/web.yaml +64 -0
  193. contextsynapse/config/sensors/earnings_calendar.yaml +52 -0
  194. contextsynapse/config/sensors/global_risk.yaml +53 -0
  195. contextsynapse/config/sensors/government_policy.yaml +54 -0
  196. contextsynapse/config/sensors/supply_chain.yaml +50 -0
  197. contextsynapse/config/sensors/templates/behavioral.yaml +56 -0
  198. contextsynapse/config/sensors/templates/equity_sentiment.yaml +48 -0
  199. contextsynapse/config/sensors/templates/global_macro.yaml +47 -0
  200. contextsynapse/config/sensors/templates/market_data.yaml +118 -0
  201. contextsynapse/config/sensors/templates/sector_technical.yaml +47 -0
  202. contextsynapse/config/sensors/weather.yaml +52 -0
  203. contextsynapse/config/storage_strategy.py +202 -0
  204. contextsynapse/config/templates/competitive_intel.yaml +39 -0
  205. contextsynapse/config/templates/geopolitical_risk.yaml +46 -0
  206. contextsynapse/config/templates/market_analysis.yaml +58 -0
  207. contextsynapse/connectors/__init__.py +15 -0
  208. contextsynapse/connectors/api_router.py +121 -0
  209. contextsynapse/connectors/base.py +357 -0
  210. contextsynapse/connectors/financial/audio.py +170 -0
  211. contextsynapse/connectors/financial/youtube.py +272 -0
  212. contextsynapse/connectors/github.py +164 -0
  213. contextsynapse/connectors/jira.py +120 -0
  214. contextsynapse/connectors/kafka_connector.py +124 -0
  215. contextsynapse/connectors/pipeline_connector.py +747 -0
  216. contextsynapse/connectors/registry.py +116 -0
  217. contextsynapse/connectors/scheduler.py +87 -0
  218. contextsynapse/connectors/universal.py +360 -0
  219. contextsynapse/context/__init__.py +96 -0
  220. contextsynapse/context/__main__.py +4 -0
  221. contextsynapse/context/acl.py +125 -0
  222. contextsynapse/context/agent_card.py +201 -0
  223. contextsynapse/context/agent_memory.py +814 -0
  224. contextsynapse/context/agents.py +472 -0
  225. contextsynapse/context/agents_redis.py +391 -0
  226. contextsynapse/context/assembled.py +204 -0
  227. contextsynapse/context/attribution.py +113 -0
  228. contextsynapse/context/blob.py +184 -0
  229. contextsynapse/context/boundaries.py +187 -0
  230. contextsynapse/context/boundary.py +507 -0
  231. contextsynapse/context/bundle.py +155 -0
  232. contextsynapse/context/cli.py +519 -0
  233. contextsynapse/context/collaboration.py +165 -0
  234. contextsynapse/context/compiler.py +604 -0
  235. contextsynapse/context/composite.py +536 -0
  236. contextsynapse/context/compression.py +147 -0
  237. contextsynapse/context/context_manager.py +888 -0
  238. contextsynapse/context/context_schema.py +90 -0
  239. contextsynapse/context/context_schema.yaml +105 -0
  240. contextsynapse/context/context_units.py +891 -0
  241. contextsynapse/context/conversation.py +488 -0
  242. contextsynapse/context/dedup.py +207 -0
  243. contextsynapse/context/document_processor.py +698 -0
  244. contextsynapse/context/embedding_hooks.py +338 -0
  245. contextsynapse/context/execution_schema.py +274 -0
  246. contextsynapse/context/fan_out.py +138 -0
  247. contextsynapse/context/frozen.py +80 -0
  248. contextsynapse/context/gravity.py +227 -0
  249. contextsynapse/context/hub.py +1401 -0
  250. contextsynapse/context/ingest.py +391 -0
  251. contextsynapse/context/injection.py +226 -0
  252. contextsynapse/context/interaction_graphifier.py +562 -0
  253. contextsynapse/context/layers.py +574 -0
  254. contextsynapse/context/projection.py +1195 -0
  255. contextsynapse/context/promotion.py +634 -0
  256. contextsynapse/context/proof.py +131 -0
  257. contextsynapse/context/propagation.py +577 -0
  258. contextsynapse/context/pubsub.py +135 -0
  259. contextsynapse/context/quality.py +1054 -0
  260. contextsynapse/context/quality_tracker.py +270 -0
  261. contextsynapse/context/scoping.py +370 -0
  262. contextsynapse/context/seed.py +992 -0
  263. contextsynapse/context/session.py +1142 -0
  264. contextsynapse/context/session_graph.py +593 -0
  265. contextsynapse/context/session_memory.py +309 -0
  266. contextsynapse/context/session_resolver.py +111 -0
  267. contextsynapse/context/skill_learning.py +237 -0
  268. contextsynapse/context/smart_budget.py +129 -0
  269. contextsynapse/context/source_policy.py +242 -0
  270. contextsynapse/context/store_factory.py +53 -0
  271. contextsynapse/context/sync.py +648 -0
  272. contextsynapse/context/token.py +236 -0
  273. contextsynapse/context/vector_integration.py +297 -0
  274. contextsynapse/context/webhooks.py +194 -0
  275. contextsynapse/context/working_memory.py +224 -0
  276. contextsynapse/core/__init__.py +18 -0
  277. contextsynapse/core/archiver.py +237 -0
  278. contextsynapse/core/change_stream.py +282 -0
  279. contextsynapse/core/checkpoint.py +310 -0
  280. contextsynapse/core/cloud_storage.py +387 -0
  281. contextsynapse/core/context_state.py +651 -0
  282. contextsynapse/core/cron.py +324 -0
  283. contextsynapse/core/datasource.py +643 -0
  284. contextsynapse/core/db.py +289 -0
  285. contextsynapse/core/federation.py +283 -0
  286. contextsynapse/core/graph_coordinator.py +201 -0
  287. contextsynapse/core/graph_intelligence.py +388 -0
  288. contextsynapse/core/graph_structures.py +106 -0
  289. contextsynapse/core/graph_sync.py +157 -0
  290. contextsynapse/core/hybrid_graph_storage.py +2998 -0
  291. contextsynapse/core/lifecycle.py +691 -0
  292. contextsynapse/core/multiworker.py +378 -0
  293. contextsynapse/core/pruning.py +312 -0
  294. contextsynapse/core/redis_registry.py +524 -0
  295. contextsynapse/core/registry.py +1043 -0
  296. contextsynapse/core/registry_factory.py +51 -0
  297. contextsynapse/core/registry_metadata.py +294 -0
  298. contextsynapse/core/replay.py +354 -0
  299. contextsynapse/core/replication.py +339 -0
  300. contextsynapse/core/time_travel.py +260 -0
  301. contextsynapse/core/write_behind.py +152 -0
  302. contextsynapse/core/write_context.py +39 -0
  303. contextsynapse/db/__init__.py +1 -0
  304. contextsynapse/db/postgres.py +267 -0
  305. contextsynapse/db/profile.py +236 -0
  306. contextsynapse/db/rules.py +328 -0
  307. contextsynapse/demo/__init__.py +0 -0
  308. contextsynapse/demo/report.py +147 -0
  309. contextsynapse/demo/runner.py +520 -0
  310. contextsynapse/demo/scenario.py +165 -0
  311. contextsynapse/demo/session_demo.py +280 -0
  312. contextsynapse/demo/session_report.py +143 -0
  313. contextsynapse/demo/universal_runner.py +257 -0
  314. contextsynapse/engine/__init__.py +15 -0
  315. contextsynapse/engine/core.py +555 -0
  316. contextsynapse/extraction/__init__.py +69 -0
  317. contextsynapse/extraction/cli.py +274 -0
  318. contextsynapse/extraction/config.py +107 -0
  319. contextsynapse/extraction/examples/example_usage.py +118 -0
  320. contextsynapse/extraction/examples/sample_manifest.json +53 -0
  321. contextsynapse/extraction/examples/sample_normalized.json +185 -0
  322. contextsynapse/extraction/extract_engine.py +440 -0
  323. contextsynapse/extraction/extractors/__init__.py +37 -0
  324. contextsynapse/extraction/extractors/chat_export_extractor.py +189 -0
  325. contextsynapse/extraction/extractors/csv_extractor.py +83 -0
  326. contextsynapse/extraction/extractors/docx_extractor.py +129 -0
  327. contextsynapse/extraction/extractors/excel_extractor.py +152 -0
  328. contextsynapse/extraction/extractors/html_extractor.py +146 -0
  329. contextsynapse/extraction/extractors/pdf_extractor.py +635 -0
  330. contextsynapse/extraction/extractors/pdf_extractor_layoutparser.py +408 -0
  331. contextsynapse/extraction/extractors/register_extractors.py +63 -0
  332. contextsynapse/extraction/extractors/text_extractor.py +65 -0
  333. contextsynapse/extraction/extractors/txt_extractor.py +120 -0
  334. contextsynapse/extraction/extractors/website_extractor.py +246 -0
  335. contextsynapse/extraction/fact_extractor.py +236 -0
  336. contextsynapse/extraction/file_manager.py +353 -0
  337. contextsynapse/extraction/hierarchy.py +239 -0
  338. contextsynapse/extraction/id_generator.py +450 -0
  339. contextsynapse/extraction/layout_detector.py +409 -0
  340. contextsynapse/extraction/llm_entity_extractor.py +301 -0
  341. contextsynapse/extraction/normalized_store.py +480 -0
  342. contextsynapse/extraction/normalizer.py +594 -0
  343. contextsynapse/extraction/parquet_writer.py +598 -0
  344. contextsynapse/extraction/post_processors/__init__.py +40 -0
  345. contextsynapse/extraction/post_processors/column_reorganizer.py +507 -0
  346. contextsynapse/extraction/ray_normalization.py +233 -0
  347. contextsynapse/extraction/ray_runner.py +207 -0
  348. contextsynapse/extraction/redis_schema_registry.py +222 -0
  349. contextsynapse/extraction/registry.py +204 -0
  350. contextsynapse/extraction/schema_loader.py +663 -0
  351. contextsynapse/extraction/schema_registry.py +230 -0
  352. contextsynapse/extraction/semantic_normalizer.py +245 -0
  353. contextsynapse/extraction/version_manager.py +314 -0
  354. contextsynapse/gateway/__init__.py +24 -0
  355. contextsynapse/gateway/action_emitter.py +212 -0
  356. contextsynapse/gateway/cost_tracker.py +508 -0
  357. contextsynapse/gateway/gateway.py +377 -0
  358. contextsynapse/gateway/policy.py +303 -0
  359. contextsynapse/governance/__init__.py +13 -0
  360. contextsynapse/governance/layer.py +513 -0
  361. contextsynapse/ingestion/__init__.py +15 -0
  362. contextsynapse/ingestion/amplifier.py +288 -0
  363. contextsynapse/ingestion/async_ingest.py +463 -0
  364. contextsynapse/ingestion/chunker.py +303 -0
  365. contextsynapse/ingestion/chunking.py +58 -0
  366. contextsynapse/ingestion/cleaner.py +190 -0
  367. contextsynapse/ingestion/cleanse.py +252 -0
  368. contextsynapse/ingestion/connectors/__init__.py +38 -0
  369. contextsynapse/ingestion/connectors/base.py +300 -0
  370. contextsynapse/ingestion/connectors/chatgpt_memory.py +247 -0
  371. contextsynapse/ingestion/connectors/claude_memory.py +274 -0
  372. contextsynapse/ingestion/connectors/sap_ingestor.py +208 -0
  373. contextsynapse/ingestion/content_detector.py +80 -0
  374. contextsynapse/ingestion/content_monitor.py +502 -0
  375. contextsynapse/ingestion/dedup.py +106 -0
  376. contextsynapse/ingestion/feed.py +640 -0
  377. contextsynapse/ingestion/filters.py +461 -0
  378. contextsynapse/ingestion/graph_builder.py +705 -0
  379. contextsynapse/ingestion/input_classifier.py +307 -0
  380. contextsynapse/ingestion/job_manager.py +571 -0
  381. contextsynapse/ingestion/llm_extractor.py +610 -0
  382. contextsynapse/ingestion/orchestrator.py +116 -0
  383. contextsynapse/ingestion/parsers/__init__.py +19 -0
  384. contextsynapse/ingestion/parsers/graph_parser_registry.py +103 -0
  385. contextsynapse/ingestion/parsers/graphml_parser.py +141 -0
  386. contextsynapse/ingestion/parsers/jsonld_parser.py +171 -0
  387. contextsynapse/ingestion/parsers/rdf_parser.py +150 -0
  388. contextsynapse/ingestion/pipeline_context.py +228 -0
  389. contextsynapse/ingestion/pipeline_store.py +1195 -0
  390. contextsynapse/ingestion/queue.py +133 -0
  391. contextsynapse/ingestion/reprocess.py +179 -0
  392. contextsynapse/ingestion/scenario_router.py +224 -0
  393. contextsynapse/ingestion/schema_extractor.py +759 -0
  394. contextsynapse/ingestion/schema_validator.py +274 -0
  395. contextsynapse/ingestion/sdlc_ingest.py +87 -0
  396. contextsynapse/ingestion/smart_ingest.py +2451 -0
  397. contextsynapse/ingestion/source_connector.py +171 -0
  398. contextsynapse/ingestion/stage_executor.py +2095 -0
  399. contextsynapse/ingestion/stages/__init__.py +9 -0
  400. contextsynapse/ingestion/stages/tabular_to_graph.py +289 -0
  401. contextsynapse/ingestion/strategies.py +386 -0
  402. contextsynapse/ingestion/universal/__init__.py +4 -0
  403. contextsynapse/ingestion/universal/_operator_registry.py +59 -0
  404. contextsynapse/ingestion/universal/ingest_content.py +32 -0
  405. contextsynapse/ingestion/universal/operators/__init__.py +1 -0
  406. contextsynapse/ingestion/universal/operators/base.py +19 -0
  407. contextsynapse/ingestion/universal/operators/build_edges.py +75 -0
  408. contextsynapse/ingestion/universal/operators/cluster_topics.py +397 -0
  409. contextsynapse/ingestion/universal/operators/deduplicate.py +45 -0
  410. contextsynapse/ingestion/universal/operators/detect_signals.py +90 -0
  411. contextsynapse/ingestion/universal/operators/embed.py +80 -0
  412. contextsynapse/ingestion/universal/operators/extract_entities.py +253 -0
  413. contextsynapse/ingestion/universal/operators/extract_preferences.py +117 -0
  414. contextsynapse/ingestion/universal/operators/filter_content.py +69 -0
  415. contextsynapse/ingestion/universal/operators/index_bm25.py +52 -0
  416. contextsynapse/ingestion/universal/operators/infer_domains.py +69 -0
  417. contextsynapse/ingestion/universal/operators/link_cross_reference.py +210 -0
  418. contextsynapse/ingestion/universal/operators/parse_records.py +66 -0
  419. contextsynapse/ingestion/universal/operators/resolve_entities.py +261 -0
  420. contextsynapse/ingestion/universal/operators/scanners/__init__.py +16 -0
  421. contextsynapse/ingestion/universal/operators/scanners/edges.py +173 -0
  422. contextsynapse/ingestion/universal/operators/scanners/git.py +187 -0
  423. contextsynapse/ingestion/universal/operators/scanners/github.py +135 -0
  424. contextsynapse/ingestion/universal/operators/scanners/github_api.py +509 -0
  425. contextsynapse/ingestion/universal/operators/scanners/helpers.py +67 -0
  426. contextsynapse/ingestion/universal/operators/scanners/jira.py +65 -0
  427. contextsynapse/ingestion/universal/operators/scanners/llm_enrichment.py +82 -0
  428. contextsynapse/ingestion/universal/operators/scanners/repo.py +149 -0
  429. contextsynapse/ingestion/universal/operators/sdlc_scan.py +213 -0
  430. contextsynapse/ingestion/universal/operators/spec_scanner.py +149 -0
  431. contextsynapse/ingestion/universal/operators/store_documents.py +72 -0
  432. contextsynapse/ingestion/universal/operators/synthesize_cu.py +180 -0
  433. contextsynapse/ingestion/universal/operators/validate_gate.py +65 -0
  434. contextsynapse/ingestion/universal/parsers/__init__.py +5 -0
  435. contextsynapse/ingestion/universal/parsers/record_parser.py +203 -0
  436. contextsynapse/ingestion/universal/parsers/turn_parser.py +329 -0
  437. contextsynapse/ingestion/universal/shared_signals.py +161 -0
  438. contextsynapse/ingestion/universal/stage_executor.py +219 -0
  439. contextsynapse/ingestion/web_crawler.py +265 -0
  440. contextsynapse/intelligence/__init__.py +53 -0
  441. contextsynapse/intelligence/alerts.py +198 -0
  442. contextsynapse/intelligence/collector.py +655 -0
  443. contextsynapse/intelligence/comparison.py +198 -0
  444. contextsynapse/intelligence/config.py +56 -0
  445. contextsynapse/intelligence/conflict_detector.py +182 -0
  446. contextsynapse/intelligence/context_gaps.py +192 -0
  447. contextsynapse/intelligence/context_radar.py +135 -0
  448. contextsynapse/intelligence/correlation_engine.py +416 -0
  449. contextsynapse/intelligence/correlation_template.py +120 -0
  450. contextsynapse/intelligence/dedup.py +296 -0
  451. contextsynapse/intelligence/event_bus.py +91 -0
  452. contextsynapse/intelligence/feedback_loop.py +101 -0
  453. contextsynapse/intelligence/freshness.py +307 -0
  454. contextsynapse/intelligence/fundamental_feed.py +551 -0
  455. contextsynapse/intelligence/geo_data.py +170 -0
  456. contextsynapse/intelligence/geo_reference.py +112 -0
  457. contextsynapse/intelligence/graph_fusion.py +465 -0
  458. contextsynapse/intelligence/heuristics.py +176 -0
  459. contextsynapse/intelligence/impact_tracker.py +336 -0
  460. contextsynapse/intelligence/macro_indicators.py +319 -0
  461. contextsynapse/intelligence/ocr.py +104 -0
  462. contextsynapse/intelligence/persistence.py +267 -0
  463. contextsynapse/intelligence/pipeline_health.py +130 -0
  464. contextsynapse/intelligence/price_feed.py +221 -0
  465. contextsynapse/intelligence/rate_limiter.py +130 -0
  466. contextsynapse/intelligence/reactive.py +411 -0
  467. contextsynapse/intelligence/runtime_context.py +390 -0
  468. contextsynapse/intelligence/sentiment_decay.py +426 -0
  469. contextsynapse/intelligence/session.py +277 -0
  470. contextsynapse/intelligence/signal_hierarchy.py +438 -0
  471. contextsynapse/intelligence/signals.py +185 -0
  472. contextsynapse/intelligence/source_watcher.py +132 -0
  473. contextsynapse/intelligence/table_enricher.py +256 -0
  474. contextsynapse/intelligence/tagger.py +319 -0
  475. contextsynapse/intelligence/timeseries.py +209 -0
  476. contextsynapse/intelligence/tracker.py +318 -0
  477. contextsynapse/intelligence/watchdog.py +392 -0
  478. contextsynapse/intelligence/watchdog_manager.py +263 -0
  479. contextsynapse/intelligence/webhook_receiver.py +165 -0
  480. contextsynapse/intelligence/weight_learner.py +234 -0
  481. contextsynapse/llm/__init__.py +8 -0
  482. contextsynapse/llm/client.py +348 -0
  483. contextsynapse/llm/rate_limiter.py +166 -0
  484. contextsynapse/logging_config.py +2 -0
  485. contextsynapse/marketplace/__init__.py +7 -0
  486. contextsynapse/marketplace/registry.py +173 -0
  487. contextsynapse/mcp/__init__.py +11 -0
  488. contextsynapse/mcp/__main__.py +4 -0
  489. contextsynapse/mcp/auth_middleware.py +212 -0
  490. contextsynapse/mcp/connection_pool.py +128 -0
  491. contextsynapse/mcp/server.py +986 -0
  492. contextsynapse/mcp/session_context.py +67 -0
  493. contextsynapse/memory/__init__.py +34 -0
  494. contextsynapse/memory/engine.py +475 -0
  495. contextsynapse/memory/temporal.py +327 -0
  496. contextsynapse/metadata/__init__.py +12 -0
  497. contextsynapse/metadata/metadata_db.py +272 -0
  498. contextsynapse/metadata/metadata_query_tool.py +300 -0
  499. contextsynapse/metadata/metadata_tracker.py +425 -0
  500. contextsynapse/models/__init__.py +27 -0
  501. contextsynapse/models/embedding_service.py +360 -0
  502. contextsynapse/models/model_registry.py +228 -0
  503. contextsynapse/multiworker.py +2 -0
  504. contextsynapse/pipelines/__init__.py +1 -0
  505. contextsynapse/pipelines/executor.py +851 -0
  506. contextsynapse/pipelines/filters.py +189 -0
  507. contextsynapse/pipelines/models.py +176 -0
  508. contextsynapse/pipelines/scheduler.py +268 -0
  509. contextsynapse/playground/__init__.py +1 -0
  510. contextsynapse/playground/templates.py +85 -0
  511. contextsynapse/plugins/__init__.py +43 -0
  512. contextsynapse/plugins/api.py +66 -0
  513. contextsynapse/plugins/base.py +350 -0
  514. contextsynapse/plugins/domain_interface.py +508 -0
  515. contextsynapse/plugins/domain_loader.py +241 -0
  516. contextsynapse/plugins/loader.py +148 -0
  517. contextsynapse/plugins/registry.py +265 -0
  518. contextsynapse/project/__init__.py +11 -0
  519. contextsynapse/project/code_context.py +807 -0
  520. contextsynapse/project/connectors/__init__.py +4 -0
  521. contextsynapse/project/connectors/github_connector.py +113 -0
  522. contextsynapse/project/connectors/jira_connector.py +160 -0
  523. contextsynapse/project/graph.py +1023 -0
  524. contextsynapse/project/project_context.py +545 -0
  525. contextsynapse/project/scanner_registry.py +61 -0
  526. contextsynapse/project/schema_manager.py +322 -0
  527. contextsynapse/project/schemas/knowledge.yaml +59 -0
  528. contextsynapse/project/schemas/sdlc.yaml +369 -0
  529. contextsynapse/project/sdlc_schema.py +172 -0
  530. contextsynapse/project/spec_parser.py +85 -0
  531. contextsynapse/project/task_lock.py +181 -0
  532. contextsynapse/project/task_stream.py +203 -0
  533. contextsynapse/project/team_tools.py +537 -0
  534. contextsynapse/py.typed +0 -0
  535. contextsynapse/realtime/__init__.py +6 -0
  536. contextsynapse/realtime/collaboration.py +147 -0
  537. contextsynapse/rules/__init__.py +10 -0
  538. contextsynapse/rules/engine.py +116 -0
  539. contextsynapse/rules/evaluators/__init__.py +6 -0
  540. contextsynapse/rules/evaluators/aggregate.py +205 -0
  541. contextsynapse/rules/evaluators/custom.py +170 -0
  542. contextsynapse/rules/evaluators/pre_action.py +125 -0
  543. contextsynapse/rules/evaluators/temporal.py +129 -0
  544. contextsynapse/rules/loader.py +52 -0
  545. contextsynapse/rules/models.py +124 -0
  546. contextsynapse/rules/universal.py +393 -0
  547. contextsynapse/scheduler.py +229 -0
  548. contextsynapse/schema/__init__.py +42 -0
  549. contextsynapse/schema/compiler.py +252 -0
  550. contextsynapse/schema/composer.py +158 -0
  551. contextsynapse/schema/dedup.py +73 -0
  552. contextsynapse/schema/derivation.py +172 -0
  553. contextsynapse/schema/sdl.py +326 -0
  554. contextsynapse/schema/validation_gate.py +124 -0
  555. contextsynapse/sdk/__init__.py +63 -0
  556. contextsynapse/sdk/agent.py +214 -0
  557. contextsynapse/sdk/client.py +269 -0
  558. contextsynapse/sdk/exceptions.py +23 -0
  559. contextsynapse/sdk/models.py +78 -0
  560. contextsynapse/sdk/session.py +408 -0
  561. contextsynapse/sdk/tracked.py +218 -0
  562. contextsynapse/sdk/worker.py +266 -0
  563. contextsynapse/sdk/workspace_client.py +98 -0
  564. contextsynapse/sdk/ws.py +41 -0
  565. contextsynapse/search/__init__.py +20 -0
  566. contextsynapse/search/embedding_cache.py +140 -0
  567. contextsynapse/search/enhanced_search.py +152 -0
  568. contextsynapse/search/fulltext.py +130 -0
  569. contextsynapse/search/graph_search.py +634 -0
  570. contextsynapse/search/lmdb_index.py +1218 -0
  571. contextsynapse/search/nl_to_aiql.py +199 -0
  572. contextsynapse/search/rag.py +1346 -0
  573. contextsynapse/search/rag_cache.py +167 -0
  574. contextsynapse/search/reasoning_chain.py +231 -0
  575. contextsynapse/search/redis_search.py +207 -0
  576. contextsynapse/search/retrieval_quality.py +314 -0
  577. contextsynapse/search/semantic_query.py +455 -0
  578. contextsynapse/search/text_resolver.py +159 -0
  579. contextsynapse/search/whoosh_search.py +373 -0
  580. contextsynapse/security/__init__.py +17 -0
  581. contextsynapse/security/audit.py +297 -0
  582. contextsynapse/security/audit_logger.py +97 -0
  583. contextsynapse/security/audit_trail.py +270 -0
  584. contextsynapse/security/auth_unified.py +284 -0
  585. contextsynapse/security/auto_tagger.py +230 -0
  586. contextsynapse/security/data_security.py +218 -0
  587. contextsynapse/security/encryption.py +207 -0
  588. contextsynapse/security/identity.py +447 -0
  589. contextsynapse/security/jwt_identity.py +59 -0
  590. contextsynapse/security/middleware.py +336 -0
  591. contextsynapse/security/pii.py +233 -0
  592. contextsynapse/security/rbac.py +96 -0
  593. contextsynapse/security/rls.py +205 -0
  594. contextsynapse/security/sanitize.py +233 -0
  595. contextsynapse/security/scoped_encryption.py +131 -0
  596. contextsynapse/security/tenant.py +401 -0
  597. contextsynapse/shield/__init__.py +21 -0
  598. contextsynapse/shield/anomaly.py +56 -0
  599. contextsynapse/shield/permissions.py +99 -0
  600. contextsynapse/shield/profile.py +103 -0
  601. contextsynapse/shield/shield.py +108 -0
  602. contextsynapse/shield/trust_engine.py +69 -0
  603. contextsynapse/skills/__init__.py +14 -0
  604. contextsynapse/skills/engine.py +233 -0
  605. contextsynapse/skills/loader.py +59 -0
  606. contextsynapse/skills/models.py +139 -0
  607. contextsynapse/storage/__init__.py +7 -0
  608. contextsynapse/storage/cache.py +208 -0
  609. contextsynapse/storage/columnar_store_v2.py +178 -0
  610. contextsynapse/storage/csr_graph_storage.py +587 -0
  611. contextsynapse/storage/csr_store.py +388 -0
  612. contextsynapse/storage/document_store.py +491 -0
  613. contextsynapse/storage/hnsw_index.py +294 -0
  614. contextsynapse/storage/hybrid_store_v2.py +278 -0
  615. contextsynapse/storage/lmdb_graph_storage.py +366 -0
  616. contextsynapse/storage/migration.py +279 -0
  617. contextsynapse/storage/namespace_store.py +346 -0
  618. contextsynapse/storage/pure_graph_storage.py +185 -0
  619. contextsynapse/storage/redis_graph_adapter.py +186 -0
  620. contextsynapse/storage/redis_graph_storage.py +375 -0
  621. contextsynapse/storage/router/__init__.py +29 -0
  622. contextsynapse/storage/router/duckdb_store.py +398 -0
  623. contextsynapse/storage/router/factory.py +83 -0
  624. contextsynapse/storage/router/interface.py +125 -0
  625. contextsynapse/storage/router/postgres_store.py +339 -0
  626. contextsynapse/storage/router/resolver.py +192 -0
  627. contextsynapse/storage/router/router.py +300 -0
  628. contextsynapse/storage/single_file_storage.py +391 -0
  629. contextsynapse/storage/storage_manager.py +214 -0
  630. contextsynapse/storage/storage_query_optimizer.py +314 -0
  631. contextsynapse/storage/wal.py +359 -0
  632. contextsynapse/temporal/__init__.py +0 -0
  633. contextsynapse/temporal/storage.py +106 -0
  634. contextsynapse/tests/__init__.py +11 -0
  635. contextsynapse/tests/regression/__init__.py +1 -0
  636. contextsynapse/tests/regression/add_metadata.py +395 -0
  637. contextsynapse/tests/regression/add_skip_flags.py +85 -0
  638. contextsynapse/tests/regression/cases/test_basic_create_node.yaml +20 -0
  639. contextsynapse/tests/regression/cases/test_basic_select.yaml +21 -0
  640. contextsynapse/tests/regression/cases/test_blockchain_audit_trail.yaml +37 -0
  641. contextsynapse/tests/regression/cases/test_blockchain_audit_trail_filtered.yaml +39 -0
  642. contextsynapse/tests/regression/cases/test_blockchain_blocks_range.yaml +39 -0
  643. contextsynapse/tests/regression/cases/test_blockchain_get_block.yaml +35 -0
  644. contextsynapse/tests/regression/cases/test_blockchain_latest_block.yaml +35 -0
  645. contextsynapse/tests/regression/cases/test_blockchain_length.yaml +37 -0
  646. contextsynapse/tests/regression/cases/test_blockchain_merkle_root.yaml +37 -0
  647. contextsynapse/tests/regression/cases/test_blockchain_verify.yaml +35 -0
  648. contextsynapse/tests/regression/cases/test_blockchain_verify_block.yaml +35 -0
  649. contextsynapse/tests/regression/cases/test_create_edge.yaml +28 -0
  650. contextsynapse/tests/regression/cases/test_delete_edge_basic.yaml +29 -0
  651. contextsynapse/tests/regression/cases/test_delete_node_basic.yaml +29 -0
  652. contextsynapse/tests/regression/cases/test_evaluation_mrr.yaml +40 -0
  653. contextsynapse/tests/regression/cases/test_evaluation_ndcg.yaml +40 -0
  654. contextsynapse/tests/regression/cases/test_evaluation_precision.yaml +40 -0
  655. contextsynapse/tests/regression/cases/test_evaluation_recall.yaml +40 -0
  656. contextsynapse/tests/regression/cases/test_group_by_basic.yaml +56 -0
  657. contextsynapse/tests/regression/cases/test_match_node_basic.yaml +29 -0
  658. contextsynapse/tests/regression/cases/test_match_node_with_order_limit.yaml +31 -0
  659. contextsynapse/tests/regression/cases/test_namespace_switch.yaml +18 -0
  660. contextsynapse/tests/regression/cases/test_pipeline_cascaded.yaml +61 -0
  661. contextsynapse/tests/regression/cases/test_pipeline_fixed_size_chunking.yaml +48 -0
  662. contextsynapse/tests/regression/cases/test_pipeline_gpt4_chunking.yaml +54 -0
  663. contextsynapse/tests/regression/cases/test_pipeline_hybrid_indexing.yaml +57 -0
  664. contextsynapse/tests/regression/cases/test_pipeline_large_embedding.yaml +52 -0
  665. contextsynapse/tests/regression/cases/test_pipeline_paragraph_chunking.yaml +49 -0
  666. contextsynapse/tests/regression/cases/test_pipeline_recursive_chunking.yaml +47 -0
  667. contextsynapse/tests/regression/cases/test_pipeline_run_pipeline.yaml +56 -0
  668. contextsynapse/tests/regression/cases/test_pipeline_section_based_chunking.yaml +53 -0
  669. contextsynapse/tests/regression/cases/test_pipeline_semantic_chunking.yaml +51 -0
  670. contextsynapse/tests/regression/cases/test_pipeline_with_entity_extraction.yaml +57 -0
  671. contextsynapse/tests/regression/cases/test_pipeline_with_relationships.yaml +62 -0
  672. contextsynapse/tests/regression/cases/test_rag_evaluation.yaml +38 -0
  673. contextsynapse/tests/regression/cases/test_rag_graph_bfs_with_eval.yaml +48 -0
  674. contextsynapse/tests/regression/cases/test_rag_graph_centrality.yaml +40 -0
  675. contextsynapse/tests/regression/cases/test_rag_graph_centrality_eval.yaml +35 -0
  676. contextsynapse/tests/regression/cases/test_rag_graph_community.yaml +40 -0
  677. contextsynapse/tests/regression/cases/test_rag_graph_community_eval.yaml +35 -0
  678. contextsynapse/tests/regression/cases/test_rag_graph_dfs_with_eval.yaml +35 -0
  679. contextsynapse/tests/regression/cases/test_rag_graph_multi_hop_eval.yaml +35 -0
  680. contextsynapse/tests/regression/cases/test_rag_graph_path.yaml +40 -0
  681. contextsynapse/tests/regression/cases/test_rag_graph_search_basic.yaml +38 -0
  682. contextsynapse/tests/regression/cases/test_rag_graph_search_bfs.yaml +52 -0
  683. contextsynapse/tests/regression/cases/test_rag_graph_search_dfs.yaml +41 -0
  684. contextsynapse/tests/regression/cases/test_rag_graph_search_multi_hop.yaml +43 -0
  685. contextsynapse/tests/regression/cases/test_rag_graph_search_shortest_path.yaml +41 -0
  686. contextsynapse/tests/regression/cases/test_rag_graph_search_weighted.yaml +43 -0
  687. contextsynapse/tests/regression/cases/test_rag_graph_shortest_path_eval.yaml +35 -0
  688. contextsynapse/tests/regression/cases/test_rag_graph_traversal.yaml +30 -0
  689. contextsynapse/tests/regression/cases/test_rag_graph_weighted_eval.yaml +35 -0
  690. contextsynapse/tests/regression/cases/test_rag_hybrid_search.yaml +33 -0
  691. contextsynapse/tests/regression/cases/test_rag_hybrid_with_prompt_embedding.yaml +44 -0
  692. contextsynapse/tests/regression/cases/test_rag_keyword_search.yaml +32 -0
  693. contextsynapse/tests/regression/cases/test_rag_multi_hop_graph.yaml +32 -0
  694. contextsynapse/tests/regression/cases/test_rag_path_based.yaml +34 -0
  695. contextsynapse/tests/regression/cases/test_rag_profile_based.yaml +32 -0
  696. contextsynapse/tests/regression/cases/test_rag_vector_search.yaml +32 -0
  697. contextsynapse/tests/regression/cases/test_rag_weighted_graph.yaml +34 -0
  698. contextsynapse/tests/regression/cases/test_rag_weighted_hybrid.yaml +31 -0
  699. contextsynapse/tests/regression/cases/test_semantic_boundary_chunking.yaml +57 -0
  700. contextsynapse/tests/regression/cases/test_semantic_boundary_detection.yaml +33 -0
  701. contextsynapse/tests/regression/cases/test_semantic_hash_function.yaml +30 -0
  702. contextsynapse/tests/regression/cases/test_semantic_hash_with_model.yaml +31 -0
  703. contextsynapse/tests/regression/cases/test_tcs_ingestion_fixed_size.yaml +44 -0
  704. contextsynapse/tests/regression/cases/test_tcs_ingestion_recursive.yaml +46 -0
  705. contextsynapse/tests/regression/cases/test_tcs_ingestion_section.yaml +46 -0
  706. contextsynapse/tests/regression/cases/test_tcs_ingestion_semantic.yaml +44 -0
  707. contextsynapse/tests/regression/cases/test_time_travel_as_of.yaml +35 -0
  708. contextsynapse/tests/regression/cases/test_time_travel_at_commit.yaml +33 -0
  709. contextsynapse/tests/regression/cases/test_time_travel_at_timestamp.yaml +33 -0
  710. contextsynapse/tests/regression/cases/test_time_travel_for_system_time_all.yaml +37 -0
  711. contextsynapse/tests/regression/cases/test_time_travel_versions_between.yaml +39 -0
  712. contextsynapse/tests/regression/cases/test_traverse_basic.yaml +31 -0
  713. contextsynapse/tests/regression/cases/test_traverse_max_depth.yaml +34 -0
  714. contextsynapse/tests/regression/cases/test_update_edge_basic.yaml +29 -0
  715. contextsynapse/tests/regression/evaluation_metrics.py +227 -0
  716. contextsynapse/tests/regression/regression_runner.py +1081 -0
  717. contextsynapse/tests/regression/setup_regression_data.py +212 -0
  718. contextsynapse/tests/regression/test_basic_only.py +84 -0
  719. contextsynapse/tests/regression/test_namespace_creation.py +29 -0
  720. contextsynapse/tests/regression/test_single_fix.py +61 -0
  721. contextsynapse/tests/regression/test_single_query.py +20 -0
  722. contextsynapse/tests/regression/validate_test_syntax.py +179 -0
  723. contextsynapse/tests/test_advanced_features.py +347 -0
  724. contextsynapse/tests/test_aiql_summary.py +283 -0
  725. contextsynapse/tests/test_all_aiql_queries.py +948 -0
  726. contextsynapse/tests/test_all_create_queries.py +213 -0
  727. contextsynapse/tests/test_api_vs_native.py +280 -0
  728. contextsynapse/tests/test_chunking_dedup.py +199 -0
  729. contextsynapse/tests/test_connectors.py +171 -0
  730. contextsynapse/tests/test_edges_and_match.py +202 -0
  731. contextsynapse/tests/test_extraction_pipeline_integration.py +581 -0
  732. contextsynapse/tests/test_name_property.py +224 -0
  733. contextsynapse/tests/test_node_edge_metadata.py +250 -0
  734. contextsynapse/tests/test_playground_conn.py +114 -0
  735. contextsynapse/tests/test_query_cache.py +213 -0
  736. contextsynapse/tests/test_source_policy_auth.py +348 -0
  737. contextsynapse/tests/test_temporal_and_domain.py +224 -0
  738. contextsynapse/tests/test_time_travel_e2e.py +571 -0
  739. contextsynapse/tests/test_traversal_and_aggregation.py +275 -0
  740. contextsynapse/tools/__init__.py +36 -0
  741. contextsynapse/tools/a2a_tools.py +106 -0
  742. contextsynapse/tools/cognition_tools.py +118 -0
  743. contextsynapse/tools/context.py +905 -0
  744. contextsynapse/tools/formatting.py +92 -0
  745. contextsynapse/tools/gateway_tools.py +186 -0
  746. contextsynapse/tools/graph.py +1700 -0
  747. contextsynapse/tools/intelligence.py +235 -0
  748. contextsynapse/tools/memory.py +118 -0
  749. contextsynapse/tools/registry.py +373 -0
  750. contextsynapse/tools/shield_tools.py +159 -0
  751. contextsynapse/tools/tasks.py +341 -0
  752. contextsynapse/tools/workspace_tools.py +124 -0
  753. contextsynapse/utils/__init__.py +15 -0
  754. contextsynapse/utils/config_loader.py +198 -0
  755. contextsynapse/utils/env_loader.py +156 -0
  756. contextsynapse/utils/file_utils.py +288 -0
  757. contextsynapse/utils/logging_optimizer.py +119 -0
  758. contextsynapse/vector/__init__.py +30 -0
  759. contextsynapse/vector/chroma_vector_store.py +152 -0
  760. contextsynapse/vector/custom_vector_store.py +537 -0
  761. contextsynapse/vector/faiss_vector_store.py +296 -0
  762. contextsynapse/vector/qdrant_vector_store.py +163 -0
  763. contextsynapse/vector/vector_db.py +953 -0
  764. contextsynapse/vector/vector_db_manager.py +197 -0
  765. contextsynapse/workspace/__init__.py +27 -0
  766. contextsynapse/workspace/base.py +94 -0
  767. contextsynapse/workspace/git.py +228 -0
  768. contextsynapse/workspace/github.py +78 -0
  769. contextsynapse/workspace/local.py +73 -0
  770. contextsynapse/workspace/tools.py +222 -0
  771. contextsynapse-1.0.0.dist-info/METADATA +847 -0
  772. contextsynapse-1.0.0.dist-info/RECORD +776 -0
  773. contextsynapse-1.0.0.dist-info/WHEEL +5 -0
  774. contextsynapse-1.0.0.dist-info/entry_points.txt +4 -0
  775. contextsynapse-1.0.0.dist-info/licenses/LICENSE +200 -0
  776. contextsynapse-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,891 @@
1
+ """
2
+ Context Units — Self-Discoverable Intelligence Layer
3
+ =====================================================
4
+ A ContextUnit (CU) is a synthesized intelligence object that bundles related
5
+ facts, entities, and events into a single addressable knowledge packet.
6
+
7
+ CUs answer questions, expose what they know, and hint at what to look for next.
8
+
9
+ Key functions:
10
+ - cluster_facts_for_cus() — group facts by shared keywords into candidate clusters
11
+ - build_context_unit() — build a CU dict from a topic + facts + entities
12
+ - match_intent() — find CUs relevant to a natural-language query
13
+ - format_cu_for_agent() — render a CU as human-readable text
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ import re
20
+ import uuid
21
+ from collections import Counter, defaultdict
22
+ from typing import Any, Dict, List, Optional
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # ── Stop words (excluded from keyword matching) ─────────────────────────────
27
+
28
+ _STOP_WORDS = frozenset({
29
+ "a", "an", "the", "is", "are", "was", "were", "be", "been", "being",
30
+ "have", "has", "had", "do", "does", "did", "will", "would", "could",
31
+ "should", "may", "might", "shall", "can", "need", "dare", "ought",
32
+ "used", "to", "of", "in", "on", "at", "by", "for", "with", "about",
33
+ "against", "between", "into", "through", "during", "before", "after",
34
+ "above", "below", "from", "up", "down", "out", "off", "over", "under",
35
+ "again", "then", "once", "that", "this", "these", "those", "what",
36
+ "which", "who", "whom", "when", "where", "why", "how", "and", "but",
37
+ "or", "nor", "not", "so", "yet", "both", "either", "neither", "if",
38
+ "as", "it", "its", "he", "she", "they", "we", "i", "you", "me", "him",
39
+ "her", "us", "them", "my", "your", "his", "our", "their", "there",
40
+ "says", "said", "say", "s", "whats", "happening", "happening",
41
+ })
42
+
43
+
44
+ def _tokenize(text: str) -> list[str]:
45
+ """Lowercase, split on non-word chars, strip stop words."""
46
+ tokens = re.findall(r"[a-z]+", text.lower())
47
+ return [t for t in tokens if t not in _STOP_WORDS and len(t) > 2]
48
+
49
+
50
+ def _extract_features(text: str) -> list[str]:
51
+ """Extract unigrams + bigrams + capitalized entities for richer TF-IDF."""
52
+ # Unigrams (lowercase, no stop words)
53
+ words = re.findall(r"[a-z]+", text.lower())
54
+ unigrams = [w for w in words if w not in _STOP_WORDS and len(w) > 2]
55
+
56
+ # Bigrams — capture phrases like "nuclear_talks", "climate_summit"
57
+ bigrams = [f"{unigrams[i]}_{unigrams[i+1]}" for i in range(len(unigrams) - 1)]
58
+
59
+ # Capitalized words from original text — proper nouns (Iran, India, OpenAI, etc.)
60
+ entities = re.findall(r"\b[A-Z][a-z]{2,}\b", text)
61
+ entity_features = [f"ENT_{e.lower()}" for e in entities]
62
+
63
+ return unigrams + bigrams + entity_features
64
+
65
+
66
+ # ── Clustering ───────────────────────────────────────────────────────────────
67
+
68
+
69
+ def cluster_facts_for_cus(
70
+ facts: List[Dict[str, Any]],
71
+ min_cluster_size: int = 3,
72
+ max_clusters: int = 15,
73
+ db=None,
74
+ ) -> List[Dict[str, Any]]:
75
+ """
76
+ Group facts into clusters using graph-aware entity co-occurrence.
77
+
78
+ Strategy (best → fallback):
79
+ 1. **Graph-aware** (if db provided): facts sharing entities (via MENTIONS edges)
80
+ cluster together. A fact about "Iran" and "Nuclear talks" clusters with
81
+ another fact about "Iran" and "IAEA" because they share the Iran entity.
82
+ 2. **Embedding-based**: cached Qdrant vectors + k-means.
83
+ 3. **TF-IDF**: unigram+bigram+entity features + k-means.
84
+
85
+ Returns a list of cluster dicts:
86
+ {"topic": str, "facts": [fact_dict, ...]}
87
+ """
88
+ if not facts:
89
+ return []
90
+
91
+ import math
92
+ n = len(facts)
93
+ if n < 30:
94
+ k = max(2, int(math.sqrt(n)))
95
+ else:
96
+ k = max(3, min(max_clusters, max(n // 15, int(math.sqrt(n)))))
97
+
98
+ # Strategy 1: Graph-aware clustering via entity co-occurrence
99
+ if db is not None:
100
+ clusters = _cluster_with_graph(facts, db, k, min_cluster_size)
101
+ if clusters:
102
+ logger.info("[CU] Graph-aware clustering: %d facts → %d clusters", n, len(clusters))
103
+ return clusters
104
+
105
+ # Strategy 2: Embedding-based clustering
106
+ texts = [
107
+ fact.get("properties", {}).get("statement", "")
108
+ or fact.get("properties", {}).get("name", "")
109
+ for fact in facts
110
+ ]
111
+ clusters = _cluster_with_embeddings(facts, texts, k, min_cluster_size)
112
+ if clusters:
113
+ logger.info("[CU] Embedding-based clustering: %d facts → %d clusters", n, len(clusters))
114
+ return clusters
115
+
116
+ # Strategy 3: TF-IDF fallback
117
+ clusters = _cluster_with_tfidf(facts, texts, k, min_cluster_size)
118
+ logger.info("[CU] TF-IDF clustering: %d facts → %d clusters", n, len(clusters))
119
+ return clusters
120
+
121
+
122
+ def _cluster_with_graph(
123
+ facts: List[Dict], db, k: int, min_size: int,
124
+ ) -> List[Dict[str, Any]]:
125
+ """
126
+ Cluster facts using graph structure — entity co-occurrence.
127
+
128
+ For each fact, find which entities it connects to via MENTIONS edges
129
+ (or reverse: entities that MENTION this fact). Facts sharing entities
130
+ naturally cluster together.
131
+
132
+ Builds a fact × entity binary matrix, then runs k-means on it.
133
+ """
134
+ try:
135
+ import numpy as np
136
+ except ImportError:
137
+ return []
138
+
139
+ # Build fact_id → index mapping
140
+ fact_ids = [f.get("id", "") for f in facts]
141
+ fact_idx = {fid: i for i, fid in enumerate(fact_ids) if fid}
142
+
143
+ if not fact_idx:
144
+ return []
145
+
146
+ # Build fact→entity connections via 2-hop: Fact ←STATES← Passage →MENTIONS→ Entity
147
+ # Step 1: Scan edges to build passage→facts and passage→entities maps
148
+ entity_set: Dict[str, int] = {} # entity_id → column index
149
+ fact_entities: Dict[int, List[int]] = defaultdict(list) # fact_row → [entity_cols]
150
+ entity_names: Dict[int, str] = {}
151
+
152
+ passage_to_facts: Dict[str, List[int]] = defaultdict(list) # passage_id → [fact rows]
153
+ passage_to_entities: Dict[str, List[str]] = defaultdict(list) # passage_id → [entity_ids]
154
+
155
+ try:
156
+ all_edges = db.get_all_edges()
157
+ except Exception:
158
+ return []
159
+
160
+ for edge in all_edges:
161
+ src = getattr(edge, 'source', '')
162
+ tgt = getattr(edge, 'target', '')
163
+ label = getattr(edge, 'label', '') or getattr(edge, 'edge_type', '')
164
+
165
+ # Passage →STATES→ Fact (or Fact →DERIVED_FROM→ Passage)
166
+ if label == "STATES" and tgt in fact_idx:
167
+ passage_to_facts[src].append(fact_idx[tgt])
168
+ elif label == "DERIVED_FROM" and src in fact_idx:
169
+ passage_to_facts[tgt].append(fact_idx[src])
170
+
171
+ # Passage →MENTIONS→ Entity
172
+ if label == "MENTIONS":
173
+ passage_to_entities[src].append(tgt)
174
+
175
+ # Direct Fact →MENTIONS→ Entity (if any exist)
176
+ if label == "MENTIONS" and src in fact_idx:
177
+ eid = tgt
178
+ if eid not in entity_set:
179
+ entity_set[eid] = len(entity_set)
180
+ fact_entities[fact_idx[src]].append(entity_set[eid])
181
+
182
+ # Step 2: Bridge — facts inherit entities from their source passage
183
+ for passage_id, fact_rows in passage_to_facts.items():
184
+ entity_ids = passage_to_entities.get(passage_id, [])
185
+ for eid in entity_ids:
186
+ if eid not in entity_set:
187
+ entity_set[eid] = len(entity_set)
188
+ col = entity_set[eid]
189
+ for row in fact_rows:
190
+ fact_entities[row].append(col)
191
+
192
+ # Step 3: Resolve entity names
193
+ # Build a complete ID→name map from all entity nodes upfront.
194
+ # This handles merged/deduplicated entities where edge targets point to
195
+ # pre-merge IDs that have empty properties (ghost nodes).
196
+ _all_entity_names: Dict[str, str] = {}
197
+ for _label in ("Person", "Organization", "Location", "Event"):
198
+ try:
199
+ for _n in db.get_all_nodes(label=_label):
200
+ _nid = _n.id if hasattr(_n, 'id') else ""
201
+ _props = _n.properties if hasattr(_n, 'properties') else {}
202
+ _name = _props.get("name") or _props.get("title") or ""
203
+ _name = _name.split("\n")[0].strip()[:60]
204
+ if _nid and _name:
205
+ _all_entity_names[_nid] = _name
206
+ except Exception:
207
+ pass
208
+
209
+ for eid, col in entity_set.items():
210
+ # Try the pre-built map first (fast, handles merged entities)
211
+ name = _all_entity_names.get(eid, "")
212
+ if not name:
213
+ # Fallback: direct node lookup (handles non-standard labels)
214
+ try:
215
+ node = db.get_node(eid)
216
+ if node:
217
+ props = node.properties if hasattr(node, 'properties') else {}
218
+ name = props.get("name") or props.get("title") or ""
219
+ name = name.split("\n")[0].strip()[:60]
220
+ except Exception:
221
+ pass
222
+ entity_names[col] = name if name else eid[:12]
223
+
224
+ if not entity_set or not fact_entities:
225
+ logger.debug("[CU] No graph edges found for facts — skipping graph clustering")
226
+ return []
227
+
228
+ # Build binary fact × entity matrix
229
+ n_facts = len(facts)
230
+ n_entities = len(entity_set)
231
+ mat = np.zeros((n_facts, n_entities), dtype=np.float32)
232
+
233
+ for row, cols in fact_entities.items():
234
+ for col in cols:
235
+ mat[row, col] = 1.0
236
+
237
+ # Weight by IDF — entities mentioned by many facts are less discriminative
238
+ import math as _math
239
+ for col in range(n_entities):
240
+ doc_freq = np.sum(mat[:, col] > 0)
241
+ if doc_freq > 0:
242
+ mat[:, col] *= _math.log(n_facts / doc_freq + 1)
243
+
244
+ # Normalize rows
245
+ norms = np.linalg.norm(mat, axis=1, keepdims=True)
246
+ norms[norms == 0] = 1.0
247
+ mat = mat / norms
248
+
249
+ # Filter out facts with zero entity connections (no graph signal)
250
+ connected = np.where(np.any(mat > 0, axis=1))[0]
251
+ if len(connected) < max(k, n_facts // 3):
252
+ logger.debug("[CU] Only %d/%d facts have graph connections — insufficient", len(connected), n_facts)
253
+ return []
254
+
255
+ labels = _kmeans(mat, k, max_iter=25)
256
+
257
+ # Build clusters with entity-derived topics
258
+ clusters = _build_clusters_from_labels(facts, list(range(n_facts)), labels, min_size)
259
+
260
+ # Enrich topic names with top entity names from each cluster
261
+ for cluster in clusters:
262
+ entity_counts: Counter = Counter()
263
+ for fact in cluster["facts"]:
264
+ fid = fact.get("id", "")
265
+ if fid in fact_idx:
266
+ for col in fact_entities.get(fact_idx[fid], []):
267
+ name = entity_names.get(col, "")
268
+ if name and name != "same-source":
269
+ entity_counts[name] += 1
270
+ if entity_counts:
271
+ # Filter out entity-ID-like names (ent_xxxx) — they're unresolved
272
+ real_names = [(n, c) for n, c in entity_counts.most_common(10)
273
+ if not n.startswith("ent_") and len(n) > 2]
274
+ if real_names:
275
+ top_entities = [name for name, _ in real_names[:3]]
276
+ cluster["topic"] = " / ".join(top_entities)
277
+ cluster["key_entities"] = [name for name, _ in real_names[:5]]
278
+ else:
279
+ # All entity names unresolved — fall back to keyword topic
280
+ cluster["topic"] = _derive_topic(cluster["facts"])
281
+ # Final safety: if topic still contains ent_ IDs, override with keywords
282
+ if "ent_" in cluster.get("topic", ""):
283
+ cluster["topic"] = _derive_topic(cluster["facts"])
284
+
285
+ # Final cleanup: replace any remaining ent_ ID topics with keyword-derived topics
286
+ for cluster in clusters:
287
+ if "ent_" in cluster.get("topic", ""):
288
+ cluster["topic"] = _derive_topic(cluster["facts"])
289
+
290
+ return clusters
291
+
292
+
293
+ def _cluster_with_embeddings(
294
+ facts: List[Dict], texts: List[str], k: int, min_size: int
295
+ ) -> List[Dict[str, Any]]:
296
+ """Cluster facts using cached embedding vectors + k-means."""
297
+ try:
298
+ from ..search.embedding_cache import get_embedding_cache
299
+ cache = get_embedding_cache()
300
+ except Exception:
301
+ return []
302
+
303
+ # Retrieve cached embeddings
304
+ vectors = []
305
+ indices = [] # which facts have embeddings
306
+ for i, text in enumerate(texts):
307
+ if not text.strip():
308
+ continue
309
+ vec = cache.get(text)
310
+ if vec is not None:
311
+ vectors.append(vec)
312
+ indices.append(i)
313
+
314
+ # Need at least 50% coverage and enough for k-means
315
+ if len(vectors) < max(k, len(facts) // 2):
316
+ return []
317
+
318
+ try:
319
+ import numpy as np
320
+ mat = np.array(vectors, dtype=np.float32)
321
+ # Normalize for cosine distance
322
+ norms = np.linalg.norm(mat, axis=1, keepdims=True)
323
+ norms[norms == 0] = 1.0
324
+ mat = mat / norms
325
+
326
+ labels = _kmeans(mat, k, max_iter=20)
327
+
328
+ return _build_clusters_from_labels(facts, indices, labels, min_size)
329
+ except Exception as e:
330
+ logger.debug("[CU] Embedding clustering failed: %s", e)
331
+ return []
332
+
333
+
334
+ def _cluster_with_tfidf(
335
+ facts: List[Dict], texts: List[str], k: int, min_size: int, _depth: int = 0,
336
+ ) -> List[Dict[str, Any]]:
337
+ """Cluster facts using TF-IDF vectors + k-means. Pure Python, no sklearn."""
338
+ # Build TF-IDF matrix with unigrams + bigrams + entity features
339
+ doc_tokens = [_extract_features(t) for t in texts]
340
+ # Vocabulary: tokens that appear in 2+ docs but <40% of docs
341
+ doc_freq: Dict[str, int] = Counter()
342
+ for tokens in doc_tokens:
343
+ for t in set(tokens):
344
+ doc_freq[t] += 1
345
+
346
+ n_docs = len(doc_tokens)
347
+ # Include all terms appearing in 2+ docs. IDF naturally down-weights common ones.
348
+ # Only exclude terms in >80% of docs (truly universal noise like "report", "news").
349
+ max_df = max(int(n_docs * 0.8), 3)
350
+ vocab = {t: idx for idx, (t, df) in enumerate(
351
+ (t, df) for t, df in doc_freq.items() if 2 <= df <= max_df
352
+ )}
353
+
354
+ if len(vocab) < 3:
355
+ # Not enough distinctive terms — return single cluster
356
+ return [{"topic": _derive_topic(facts), "facts": facts}]
357
+
358
+ import math as _math
359
+
360
+ # Build sparse TF-IDF vectors as dicts
361
+ idf = {t: _math.log(n_docs / df) for t, df in doc_freq.items() if t in vocab}
362
+ vectors = []
363
+ indices = []
364
+ for i, tokens in enumerate(doc_tokens):
365
+ if not tokens:
366
+ continue
367
+ tf = Counter(tokens)
368
+ vec = {}
369
+ for t, count in tf.items():
370
+ if t in vocab:
371
+ vec[vocab[t]] = (count / len(tokens)) * idf.get(t, 1.0)
372
+ if vec:
373
+ vectors.append(vec)
374
+ indices.append(i)
375
+
376
+ if len(vectors) < k:
377
+ return [{"topic": _derive_topic(facts), "facts": facts}]
378
+
379
+ # Convert sparse to dense for k-means
380
+ try:
381
+ import numpy as np
382
+ dim = len(vocab)
383
+ mat = np.zeros((len(vectors), dim), dtype=np.float32)
384
+ for row, vec in enumerate(vectors):
385
+ for col, val in vec.items():
386
+ mat[row, col] = val
387
+ # Normalize
388
+ norms = np.linalg.norm(mat, axis=1, keepdims=True)
389
+ norms[norms == 0] = 1.0
390
+ mat = mat / norms
391
+
392
+ labels = _kmeans(mat, k, max_iter=20)
393
+ return _build_clusters_from_labels(facts, indices, labels, min_size, _depth=_depth)
394
+ except ImportError:
395
+ # No numpy — pure Python fallback with simple keyword grouping
396
+ return _cluster_keyword_fallback(facts, min_size)
397
+
398
+
399
+ def _kmeans(mat, k: int, max_iter: int = 20) -> list:
400
+ """Lightweight k-means on a numpy matrix. Returns list of cluster labels."""
401
+ import numpy as np
402
+ n = mat.shape[0]
403
+ k = min(k, n)
404
+
405
+ # K-means++ initialization
406
+ rng = np.random.RandomState(42)
407
+ centroids = [mat[rng.randint(n)]]
408
+ for _ in range(k - 1):
409
+ dists = np.array([min(np.dot(mat[i] - c, mat[i] - c) for c in centroids) for i in range(n)])
410
+ probs = dists / (dists.sum() + 1e-12)
411
+ idx = rng.choice(n, p=probs)
412
+ centroids.append(mat[idx])
413
+ centroids = np.array(centroids)
414
+
415
+ labels = np.zeros(n, dtype=int)
416
+ for _ in range(max_iter):
417
+ # Assign
418
+ # Cosine similarity = dot product on normalized vectors
419
+ sims = mat @ centroids.T # (n, k)
420
+ new_labels = np.argmax(sims, axis=1)
421
+ if np.array_equal(new_labels, labels):
422
+ break
423
+ labels = new_labels
424
+ # Update centroids
425
+ for j in range(k):
426
+ members = mat[labels == j]
427
+ if len(members) > 0:
428
+ c = members.mean(axis=0)
429
+ norm = np.linalg.norm(c)
430
+ centroids[j] = c / norm if norm > 0 else c
431
+
432
+ return labels.tolist()
433
+
434
+
435
+ def _build_clusters_from_labels(
436
+ facts: List[Dict], indices: List[int], labels: list, min_size: int,
437
+ _depth: int = 0,
438
+ ) -> List[Dict[str, Any]]:
439
+ """Group facts by cluster labels and derive topics.
440
+ Recursively splits oversized clusters (>40% of total)."""
441
+ groups: Dict[int, List[Dict]] = defaultdict(list)
442
+ for idx, label in zip(indices, labels):
443
+ groups[label].append(facts[idx])
444
+
445
+ # Also assign un-indexed facts to nearest cluster by keyword overlap
446
+ indexed_set = set(indices)
447
+ for i, fact in enumerate(facts):
448
+ if i not in indexed_set:
449
+ if groups:
450
+ best = max(groups, key=lambda g: len(groups[g]))
451
+ groups[best].append(fact)
452
+
453
+ total = len(facts)
454
+ max_cluster_pct = 0.4 # No single cluster should be >40% of total
455
+
456
+ clusters = []
457
+ for label, cluster_facts in sorted(groups.items(), key=lambda x: -len(x[1])):
458
+ if len(cluster_facts) < min_size:
459
+ continue
460
+
461
+ # Recursively split oversized clusters (max 2 levels deep)
462
+ if len(cluster_facts) > total * max_cluster_pct and _depth < 2 and len(cluster_facts) >= 6:
463
+ sub_texts = [
464
+ f.get("properties", {}).get("statement", "")
465
+ or f.get("properties", {}).get("name", "")
466
+ for f in cluster_facts
467
+ ]
468
+ import math
469
+ sub_k = max(2, min(5, len(cluster_facts) // 10))
470
+ sub_clusters = _cluster_with_tfidf(cluster_facts, sub_texts, sub_k, min_size, _depth=_depth + 1)
471
+ if len(sub_clusters) > 1:
472
+ clusters.extend(sub_clusters)
473
+ continue
474
+
475
+ topic = _derive_topic(cluster_facts)
476
+ clusters.append({"topic": topic, "facts": cluster_facts})
477
+
478
+ # If min_size filtered everything, return top clusters with relaxed threshold
479
+ if not clusters and groups:
480
+ for label, cluster_facts in sorted(groups.items(), key=lambda x: -len(x[1])):
481
+ if len(cluster_facts) >= 2:
482
+ topic = _derive_topic(cluster_facts)
483
+ clusters.append({"topic": topic, "facts": cluster_facts})
484
+ if len(clusters) >= 3:
485
+ break
486
+
487
+ return clusters
488
+
489
+
490
+ def _derive_topic(facts: List[Dict]) -> str:
491
+ """Derive a human-readable topic label from the most frequent keywords."""
492
+ all_text = " ".join(
493
+ f.get("properties", {}).get("statement", "") for f in facts
494
+ )
495
+ freq = Counter(_tokenize(all_text))
496
+ # Filter out very common words within this cluster (>60% of facts)
497
+ n = len(facts)
498
+ top = [(kw, c) for kw, c in freq.most_common(10) if c < n * 0.6]
499
+ if not top:
500
+ top = freq.most_common(3)
501
+ top_kws = [kw for kw, _ in top[:3]]
502
+ return " ".join(w.capitalize() for w in top_kws) or "General"
503
+
504
+
505
+ def _cluster_keyword_fallback(facts: List[Dict], min_size: int) -> List[Dict[str, Any]]:
506
+ """Simple keyword-based grouping when numpy is unavailable. Uses TF-IDF scores
507
+ to pick the top distinctive keyword per fact, then groups by that keyword."""
508
+ doc_tokens = [_tokenize(
509
+ f.get("properties", {}).get("statement", "") or f.get("properties", {}).get("name", "")
510
+ ) for f in facts]
511
+
512
+ doc_freq: Dict[str, int] = Counter()
513
+ for tokens in doc_tokens:
514
+ for t in set(tokens):
515
+ doc_freq[t] += 1
516
+
517
+ import math as _math
518
+ n_docs = len(facts)
519
+ idf = {t: _math.log(n_docs / df) for t, df in doc_freq.items() if df >= 2}
520
+
521
+ groups: Dict[str, List[Dict]] = defaultdict(list)
522
+ for i, tokens in enumerate(doc_tokens):
523
+ if not tokens:
524
+ groups["general"].append(facts[i])
525
+ continue
526
+ tf = Counter(tokens)
527
+ # Pick the keyword with highest TF-IDF score as this fact's "topic"
528
+ best_kw = max(
529
+ (t for t in tokens if t in idf),
530
+ key=lambda t: (tf[t] / len(tokens)) * idf[t],
531
+ default="general",
532
+ )
533
+ groups[best_kw].append(facts[i])
534
+
535
+ # Merge tiny groups into "other"
536
+ clusters = []
537
+ other: List[Dict] = []
538
+ for kw, group_facts in sorted(groups.items(), key=lambda x: -len(x[1])):
539
+ if len(group_facts) >= min_size:
540
+ clusters.append({"topic": kw.capitalize(), "facts": group_facts})
541
+ else:
542
+ other.extend(group_facts)
543
+ if other and len(other) >= min_size:
544
+ clusters.append({"topic": _derive_topic(other), "facts": other})
545
+
546
+ return clusters or [{"topic": _derive_topic(facts), "facts": facts}]
547
+
548
+
549
+ # ── CU Builder ───────────────────────────────────────────────────────────────
550
+
551
+
552
+ def build_context_unit(
553
+ topic: str,
554
+ facts: List[Dict[str, Any]],
555
+ entities: Optional[List[Dict[str, Any]]] = None,
556
+ events: Optional[List[Dict[str, Any]]] = None,
557
+ use_llm: bool = False,
558
+ llm_client: Any = None,
559
+ cu_config: Any = None,
560
+ ) -> Dict[str, Any]:
561
+ """
562
+ Build a ContextUnit dict from a topic, facts, entities, and events.
563
+
564
+ Parameters
565
+ ----------
566
+ topic : Human-readable label for this CU's subject matter.
567
+ facts : List of Fact node dicts (with 'id' and 'properties').
568
+ entities : List of Person/Organization/Location node dicts.
569
+ events : List of Event node dicts.
570
+ use_llm : If True and llm_client is provided, synthesize claim via LLM.
571
+ llm_client : Optional LLM client (must expose .complete(prompt) → str).
572
+ cu_config : Optional ContextUnitConfig from schema (domain-aware CU rules).
573
+
574
+ Returns
575
+ -------
576
+ A ContextUnit dict with keys:
577
+ id, label, properties (topic, claim, confidence, questions_answered,
578
+ next_clues, created_at), evidence_ids, actor_ids
579
+ """
580
+ entities = entities or []
581
+ events = events or []
582
+
583
+ # Extract raw text
584
+ statements = [
585
+ f.get("properties", {}).get("statement", "")
586
+ for f in facts
587
+ if f.get("properties", {}).get("statement")
588
+ ]
589
+ actor_names = [
590
+ e.get("properties", {}).get("name", "")
591
+ for e in entities
592
+ if e.get("properties", {}).get("name")
593
+ ]
594
+ event_names = [
595
+ ev.get("properties", {}).get("name", "")
596
+ for ev in events
597
+ if ev.get("properties", {}).get("name")
598
+ ]
599
+
600
+ evidence_ids = [f.get("id") for f in facts if f.get("id")]
601
+ actor_ids = [e.get("id") for e in entities if e.get("id")]
602
+
603
+ # Build properties via LLM or heuristic
604
+ if use_llm and llm_client is not None:
605
+ props = _build_cu_with_llm(topic, statements, actor_names, event_names, llm_client)
606
+ else:
607
+ props = _build_cu_heuristic(topic, statements, actor_names, event_names)
608
+
609
+ # Sanitize topic — replace unresolved entity IDs with keyword-derived topic
610
+ if "ent_" in topic:
611
+ all_text = " ".join(statements[:20])
612
+ freq = Counter(_tokenize(all_text))
613
+ top_kws = [kw for kw, _ in freq.most_common(3)]
614
+ topic = " ".join(w.capitalize() for w in top_kws) or topic
615
+
616
+ props["topic"] = topic
617
+
618
+ # Apply domain-specific overrides from schema's context_unit config
619
+ # (runs after topic is set so claim_template can reference it)
620
+ if cu_config is not None:
621
+ props = _apply_cu_config(props, facts, cu_config)
622
+
623
+ cu_id = f"cu_{uuid.uuid4().hex[:12]}"
624
+ return {
625
+ "id": cu_id,
626
+ "label": "ContextUnit",
627
+ "properties": props,
628
+ "evidence_ids": evidence_ids,
629
+ "actor_ids": actor_ids,
630
+ }
631
+
632
+
633
+ def _build_cu_with_llm(
634
+ topic: str,
635
+ statements: List[str],
636
+ actor_names: List[str],
637
+ event_names: List[str],
638
+ llm_client: Any,
639
+ ) -> Dict[str, Any]:
640
+ """Synthesize CU properties using an LLM client."""
641
+ facts_text = "\n".join(f"- {s}" for s in statements[:10])
642
+ actors_text = ", ".join(actor_names) if actor_names else "unknown"
643
+ prompt = (
644
+ f"You are a news analyst. Given the following facts about '{topic}', "
645
+ f"write:\n1. A single-sentence claim summarising the situation.\n"
646
+ f"2. Two questions this cluster of facts answers.\n"
647
+ f"3. One next clue to follow up.\n\n"
648
+ f"Facts:\n{facts_text}\n\nActors: {actors_text}\n\n"
649
+ f"Respond as JSON with keys: claim, questions_answered (list), next_clues (list)."
650
+ )
651
+ try:
652
+ raw = llm_client.complete(prompt)
653
+ import json as _json
654
+ data = _json.loads(raw)
655
+ confidence = min(0.95, 0.5 + 0.05 * len(statements))
656
+ return {
657
+ "claim": data.get("claim", statements[0] if statements else topic),
658
+ "confidence": confidence,
659
+ "questions_answered": data.get("questions_answered", []),
660
+ "next_clues": data.get("next_clues", []),
661
+ }
662
+ except Exception as exc:
663
+ logger.warning("LLM synthesis failed, falling back to heuristic: %s", exc)
664
+ return _build_cu_heuristic(topic, statements, actor_names, event_names)
665
+
666
+
667
+ def _build_cu_heuristic(
668
+ topic: str,
669
+ statements: List[str],
670
+ actor_names: List[str],
671
+ event_names: List[str],
672
+ ) -> Dict[str, Any]:
673
+ """
674
+ Build CU properties without an LLM.
675
+
676
+ - claim : first 1-2 fact statements joined
677
+ - confidence : 0.3 + 0.1 * num_facts, capped at 0.95
678
+ - questions_answered : derived from topic and actor names
679
+ - next_clues : derived from actor and event names
680
+ """
681
+ # Claim: concatenate first two statements
682
+ if statements:
683
+ claim_parts = [s.rstrip(".") for s in statements[:2]]
684
+ claim = "; ".join(claim_parts) + "."
685
+ else:
686
+ claim = f"Situation update on {topic}."
687
+
688
+ confidence = min(0.95, 0.3 + 0.1 * len(statements))
689
+
690
+ # Questions answered
691
+ questions: list[str] = [f"What is the current situation regarding {topic}?"]
692
+ for name in actor_names[:2]:
693
+ questions.append(f"What is {name}'s role in {topic}?")
694
+
695
+ # Next clues
696
+ next_clues: list[str] = []
697
+ for name in actor_names[:2]:
698
+ next_clues.append(f"Follow up on {name}'s statements.")
699
+ for name in event_names[:2]:
700
+ next_clues.append(f"Track developments in {name}.")
701
+ if not next_clues:
702
+ next_clues.append(f"Search for latest updates on {topic}.")
703
+
704
+ return {
705
+ "claim": claim,
706
+ "confidence": confidence,
707
+ "questions_answered": questions,
708
+ "next_clues": next_clues,
709
+ }
710
+
711
+
712
+ def _apply_cu_config(
713
+ props: Dict[str, Any],
714
+ facts: List[Dict[str, Any]],
715
+ cu_config: Any,
716
+ ) -> Dict[str, Any]:
717
+ """Apply domain-specific ContextUnitConfig overrides to CU properties.
718
+
719
+ - confidence_weights: replaces flat 0.1-per-fact with evidence-type-weighted scoring
720
+ - claim_template: domain-specific claim phrasing (if non-empty)
721
+ - domain_questions: prepends domain seed questions to questions_answered
722
+ """
723
+ # 1. Domain-aware confidence scoring
724
+ weights = getattr(cu_config, "confidence_weights", {})
725
+ if weights:
726
+ total_weight = 0.0
727
+ matched = 0
728
+ for fact in facts:
729
+ fprops = fact.get("properties", {})
730
+ # Check fact's evidence_level, evidence_type, type, or label
731
+ ev_type = (
732
+ fprops.get("evidence_level")
733
+ or fprops.get("evidence_type")
734
+ or fprops.get("type", "")
735
+ ).lower().replace(" ", "_")
736
+ w = weights.get(ev_type, 0.0)
737
+ if w > 0:
738
+ total_weight += w
739
+ matched += 1
740
+ else:
741
+ # Unclassified evidence gets a small default weight
742
+ total_weight += 0.05
743
+ # Base 0.2 + accumulated evidence weight, capped at 0.95
744
+ props["confidence"] = min(0.95, 0.2 + total_weight)
745
+ if matched > 0:
746
+ props["evidence_quality"] = f"{matched}/{len(facts)} typed"
747
+
748
+ # 2. Claim template
749
+ template = getattr(cu_config, "claim_template", "")
750
+ if template and props.get("claim"):
751
+ try:
752
+ props["claim"] = template.format(
753
+ topic=props.get("topic", ""),
754
+ claim=props["claim"],
755
+ evidence_type=props.get("evidence_quality", "mixed"),
756
+ fact_count=len(facts),
757
+ exposure="",
758
+ )
759
+ except (KeyError, IndexError):
760
+ pass # template has placeholders we can't fill — keep original claim
761
+
762
+ # 3. Domain questions — prepend schema-defined questions, deduplicate
763
+ domain_qs = getattr(cu_config, "domain_questions", [])
764
+ if domain_qs:
765
+ existing = set(props.get("questions_answered", []))
766
+ new_qs = [q for q in domain_qs if q not in existing]
767
+ props["questions_answered"] = new_qs + props.get("questions_answered", [])
768
+
769
+ return props
770
+
771
+
772
+ # ── Intent Matching ──────────────────────────────────────────────────────────
773
+
774
+
775
+ def match_intent(
776
+ query: str,
777
+ cus: List[Dict[str, Any]],
778
+ limit: int = 3,
779
+ ) -> List[Dict[str, Any]]:
780
+ """
781
+ Find CUs whose topic or questions_answered overlap with a query.
782
+
783
+ Scoring:
784
+ - Each query token matched in CU.topic contributes 2 points.
785
+ - Each query token matched in any questions_answered string contributes 1 point.
786
+
787
+ Returns CUs with score > 0, ranked descending, up to `limit`.
788
+ """
789
+ query_tokens = set(_tokenize(query))
790
+ if not query_tokens:
791
+ return []
792
+
793
+ scored: list[tuple[int, dict]] = []
794
+ for cu in cus:
795
+ props = cu.get("properties", {})
796
+ score = 0
797
+
798
+ topic_tokens = set(_tokenize(props.get("topic", "")))
799
+ score += 2 * len(query_tokens & topic_tokens)
800
+
801
+ for q in props.get("questions_answered", []):
802
+ q_tokens = set(_tokenize(q))
803
+ score += len(query_tokens & q_tokens)
804
+
805
+ if score > 0:
806
+ scored.append((score, cu))
807
+
808
+ scored.sort(key=lambda x: x[0], reverse=True)
809
+ return [cu for _, cu in scored[:limit]]
810
+
811
+
812
+ # ── Formatter ────────────────────────────────────────────────────────────────
813
+
814
+
815
+ def format_cu_for_agent(
816
+ cu: Dict[str, Any],
817
+ zoom: str = "full",
818
+ cu_config: Any = None,
819
+ ) -> str:
820
+ """Render a ContextUnit at a specific zoom level.
821
+
822
+ Zoom levels (like Google Maps tile resolution):
823
+ headline — ~20 tokens: topic + short claim. For instant/fast tiers.
824
+ summary — ~80 tokens: claim + evidence count + top questions. For standard tier.
825
+ full — ~200 tokens: everything including evidence IDs. For deep tier.
826
+
827
+ If cu_config has custom zoom_levels templates, those are used instead.
828
+ """
829
+ props = cu.get("properties", {})
830
+ topic = props.get("topic", "Unknown")
831
+ claim = props.get("claim", "")
832
+ confidence = props.get("confidence", 0.0)
833
+ questions = props.get("questions_answered", [])
834
+ clues = props.get("next_clues", [])
835
+ evidence = cu.get("evidence_ids", [])
836
+
837
+ # Try schema-defined zoom template first
838
+ if cu_config is not None:
839
+ templates = getattr(cu_config, "zoom_levels", {})
840
+ template = templates.get(zoom, "")
841
+ if template:
842
+ try:
843
+ claim_short = claim.split(";")[0].split(".")[0].strip()
844
+ return template.format(
845
+ topic=topic,
846
+ confidence=confidence,
847
+ claim=claim,
848
+ claim_short=claim_short[:80],
849
+ questions="; ".join(questions[:3]),
850
+ questions_short="; ".join(q[:40] for q in questions[:2]),
851
+ clues="; ".join(clues[:2]),
852
+ evidence_ids=", ".join(evidence[:5]),
853
+ evidence_count=len(evidence),
854
+ )
855
+ except (KeyError, IndexError, ValueError):
856
+ pass # fall through to default formatting
857
+
858
+ # Default zoom formatting
859
+ if zoom == "headline":
860
+ claim_short = claim.split(";")[0].split(".")[0].strip()
861
+ return f"[{topic}] {claim_short[:80]} ({confidence:.0%})"
862
+
863
+ if zoom == "summary":
864
+ lines = [
865
+ f"[{topic}] ({confidence:.0%})",
866
+ f" {claim[:150]}",
867
+ ]
868
+ if evidence:
869
+ lines.append(f" Evidence: {len(evidence)} sources")
870
+ if questions:
871
+ lines.append(f" Answers: {'; '.join(q[:40] for q in questions[:2])}")
872
+ return "\n".join(lines)
873
+
874
+ # full (default)
875
+ lines = [
876
+ f"[ContextUnit: {topic}]",
877
+ f"Claim: {claim}",
878
+ f"Confidence: {confidence:.0%}",
879
+ ]
880
+ if questions:
881
+ lines.append("Answers:")
882
+ for q in questions:
883
+ lines.append(f" - {q}")
884
+ if clues:
885
+ lines.append("Next clues:")
886
+ for c in clues:
887
+ lines.append(f" - {c}")
888
+ if evidence:
889
+ lines.append(f"Evidence: {', '.join(evidence)}")
890
+
891
+ return "\n".join(lines)