hyperagents 1.2.3rc5.post3__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 (361) hide show
  1. agents/__init__.py +31 -0
  2. agents/background_monitor.py +453 -0
  3. agents/learning.py +460 -0
  4. agents/planner.py +421 -0
  5. agents/transcriber.py +341 -0
  6. civilization/__init__.py +51 -0
  7. civilization/api/__init__.py +7 -0
  8. civilization/api/civilization_api.py +361 -0
  9. civilization/graph/__init__.py +19 -0
  10. civilization/graph/knowledge_linker.py +348 -0
  11. civilization/graph/org_graph.py +348 -0
  12. civilization/graph/process_graph.py +386 -0
  13. civilization/ingestion/__init__.py +20 -0
  14. civilization/ingestion/chunker.py +273 -0
  15. civilization/ingestion/document_ingestor.py +358 -0
  16. civilization/ingestion/embedder.py +262 -0
  17. civilization/ingestion/extractor.py +224 -0
  18. civilization/interview/__init__.py +19 -0
  19. civilization/interview/gap_detector.py +380 -0
  20. civilization/interview/interviewer.py +323 -0
  21. civilization/interview/questioner.py +363 -0
  22. civilization/retrieval/__init__.py +17 -0
  23. civilization/retrieval/civilization_rag.py +316 -0
  24. civilization/retrieval/context_injector.py +342 -0
  25. civilization/retrieval/relevance_ranker.py +287 -0
  26. civilization/schema.py +299 -0
  27. civilization/store.py +177 -0
  28. civilization/sync/__init__.py +16 -0
  29. civilization/sync/confluence_sync.py +317 -0
  30. civilization/sync/gdrive_sync.py +262 -0
  31. civilization/sync/notion_sync.py +237 -0
  32. civilization/versioning/__init__.py +19 -0
  33. civilization/versioning/diff_engine.py +351 -0
  34. civilization/versioning/staleness_detector.py +303 -0
  35. civilization/versioning/version_manager.py +293 -0
  36. cli/__init__.py +1 -0
  37. cli/doctor.py +334 -0
  38. cli/hyperclaw.py +1357 -0
  39. cli/onboarding.py +469 -0
  40. core/__init__.py +1 -0
  41. core/connectors/__init__.py +119 -0
  42. core/connectors/infrastructure.py +772 -0
  43. core/connectors/messaging.py +586 -0
  44. core/connectors/productivity.py +764 -0
  45. core/connectors/smarthome.py +528 -0
  46. core/connectors/social.py +730 -0
  47. core/hyperrouter/__init__.py +23 -0
  48. core/hyperrouter/bandit.py +218 -0
  49. core/hyperrouter/fast_loop.py +68 -0
  50. core/hyperrouter/slow_loop.py +143 -0
  51. core/hyperstate/__init__.py +24 -0
  52. core/hyperstate/certifier.py +154 -0
  53. core/hyperstate/schema.py +160 -0
  54. core/hyperstate/state_manager.py +103 -0
  55. core/hyperstate/store.py +163 -0
  56. core/providers/__init__.py +121 -0
  57. core/providers/embeddings.py +389 -0
  58. core/providers/image.py +534 -0
  59. core/providers/llm.py +1935 -0
  60. core/providers/search.py +608 -0
  61. core/providers/voice.py +474 -0
  62. hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
  63. hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
  64. hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
  65. hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
  66. hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
  67. hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
  68. hyperclaw/__init__.py +67 -0
  69. hyperclaw/__main__.py +220 -0
  70. hyperclaw/agent.py +775 -0
  71. hyperclaw/agent_coordinator.py +577 -0
  72. hyperclaw/anthropic_params.py +225 -0
  73. hyperclaw/api_utils.py +85 -0
  74. hyperclaw/approval_telegram.py +209 -0
  75. hyperclaw/approval_terminal.py +80 -0
  76. hyperclaw/attachments.py +177 -0
  77. hyperclaw/branding.py +55 -0
  78. hyperclaw/brief/__init__.py +11 -0
  79. hyperclaw/brief/cli.py +204 -0
  80. hyperclaw/brief/collect.py +146 -0
  81. hyperclaw/brief/compose.py +333 -0
  82. hyperclaw/brief/diff.py +110 -0
  83. hyperclaw/brief/errors.py +99 -0
  84. hyperclaw/brief/google.py +269 -0
  85. hyperclaw/brief/google_auth.py +294 -0
  86. hyperclaw/brief/links.py +124 -0
  87. hyperclaw/brief/service.py +424 -0
  88. hyperclaw/brief/store.py +205 -0
  89. hyperclaw/brief/tasks.py +200 -0
  90. hyperclaw/chat_scope.py +115 -0
  91. hyperclaw/cli_tui.py +497 -0
  92. hyperclaw/client_guard.py +717 -0
  93. hyperclaw/connection_check.py +113 -0
  94. hyperclaw/cost_guard.py +1728 -0
  95. hyperclaw/default_config/agents.yaml +534 -0
  96. hyperclaw/default_config/hyperclaw.yaml +27 -0
  97. hyperclaw/default_config/models.yaml +161 -0
  98. hyperclaw/deployment.py +39 -0
  99. hyperclaw/digest/__init__.py +9 -0
  100. hyperclaw/digest/cli.py +117 -0
  101. hyperclaw/digest/errors.py +100 -0
  102. hyperclaw/digest/gitlab.py +187 -0
  103. hyperclaw/digest/http.py +113 -0
  104. hyperclaw/digest/redact.py +32 -0
  105. hyperclaw/digest/service.py +271 -0
  106. hyperclaw/digest/slack.py +223 -0
  107. hyperclaw/digest/state.py +124 -0
  108. hyperclaw/digest/summarize.py +198 -0
  109. hyperclaw/digest/tokens.py +143 -0
  110. hyperclaw/distribution.py +46 -0
  111. hyperclaw/documents.py +351 -0
  112. hyperclaw/feeds.py +339 -0
  113. hyperclaw/google_workspace/__init__.py +5 -0
  114. hyperclaw/google_workspace/api.py +151 -0
  115. hyperclaw/google_workspace/auth.py +397 -0
  116. hyperclaw/google_workspace/calendar.py +157 -0
  117. hyperclaw/google_workspace/cli.py +72 -0
  118. hyperclaw/google_workspace/drive.py +236 -0
  119. hyperclaw/google_workspace/errors.py +58 -0
  120. hyperclaw/google_workspace/gmail.py +295 -0
  121. hyperclaw/google_workspace/meet.py +122 -0
  122. hyperclaw/google_workspace/tools.py +319 -0
  123. hyperclaw/hyperspeed.py +57 -0
  124. hyperclaw/integrations_layer.py +1427 -0
  125. hyperclaw/live_state.py +188 -0
  126. hyperclaw/managed_config.py +245 -0
  127. hyperclaw/media_hub.py +179 -0
  128. hyperclaw/memory_manager.py +584 -0
  129. hyperclaw/model_catalog.py +551 -0
  130. hyperclaw/model_policy.py +211 -0
  131. hyperclaw/model_router.py +595 -0
  132. hyperclaw/model_selector.py +72 -0
  133. hyperclaw/naming.py +284 -0
  134. hyperclaw/notices.py +65 -0
  135. hyperclaw/oai_stream.py +106 -0
  136. hyperclaw/orchestrator.py +591 -0
  137. hyperclaw/outbox.py +82 -0
  138. hyperclaw/persona.py +367 -0
  139. hyperclaw/prometheus.py +219 -0
  140. hyperclaw/prompt_assembly.py +157 -0
  141. hyperclaw/provider_setup.py +146 -0
  142. hyperclaw/providers.py +340 -0
  143. hyperclaw/qualification.py +341 -0
  144. hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
  145. hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
  146. hyperclaw/reasoning_display.py +35 -0
  147. hyperclaw/scheduler.py +454 -0
  148. hyperclaw/server.py +847 -0
  149. hyperclaw/setup.py +627 -0
  150. hyperclaw/skills.py +717 -0
  151. hyperclaw/solomon.py +442 -0
  152. hyperclaw/swarm.py +533 -0
  153. hyperclaw/taint.py +832 -0
  154. hyperclaw/team/__init__.py +28 -0
  155. hyperclaw/team/dispatcher.py +431 -0
  156. hyperclaw/team/genesis.py +106 -0
  157. hyperclaw/team/lead.py +110 -0
  158. hyperclaw/team/model.py +135 -0
  159. hyperclaw/team/morning.py +238 -0
  160. hyperclaw/team/repos.py +66 -0
  161. hyperclaw/team/roster.py +264 -0
  162. hyperclaw/team/specialist.py +235 -0
  163. hyperclaw/team/status.py +163 -0
  164. hyperclaw/team/watchers.py +112 -0
  165. hyperclaw/teamsetup/__init__.py +17 -0
  166. hyperclaw/teamsetup/checks.py +208 -0
  167. hyperclaw/teamsetup/item.py +391 -0
  168. hyperclaw/teamsetup/oauth_loopback.py +84 -0
  169. hyperclaw/teamsetup/onboard.py +550 -0
  170. hyperclaw/teamsetup/profile.py +82 -0
  171. hyperclaw/teamsetup/secrets.py +313 -0
  172. hyperclaw/teamsetup/selfcheck.py +295 -0
  173. hyperclaw/teamsetup/service.py +495 -0
  174. hyperclaw/teamsetup/slack_oauth.py +442 -0
  175. hyperclaw/teamsetup/tty.py +62 -0
  176. hyperclaw/telegram_bot.py +1106 -0
  177. hyperclaw/telegram_intake.py +126 -0
  178. hyperclaw/telegram_pairing.py +360 -0
  179. hyperclaw/terminal_provider.py +323 -0
  180. hyperclaw/tools/__init__.py +1 -0
  181. hyperclaw/tools/approval.py +949 -0
  182. hyperclaw/tools/args.py +212 -0
  183. hyperclaw/tools/catalog.py +244 -0
  184. hyperclaw/tools/classify.py +266 -0
  185. hyperclaw/tools/effects.py +724 -0
  186. hyperclaw/tools/egress.py +210 -0
  187. hyperclaw/tools/envelope.py +188 -0
  188. hyperclaw/tools/guard.py +367 -0
  189. hyperclaw/tools/ledger.py +339 -0
  190. hyperclaw/tools/receipts.py +70 -0
  191. hyperclaw/tools/registry.py +318 -0
  192. hyperclaw/tools/service_guard.py +151 -0
  193. hyperclaw/tools/surfaces.py +54 -0
  194. hyperclaw/tools/workspace.py +87 -0
  195. hyperclaw/trading/__init__.py +77 -0
  196. hyperclaw/truth_guard.py +791 -0
  197. hyperclaw/tts.py +124 -0
  198. hyperclaw/tui.py +4255 -0
  199. hyperclaw/tui_bridge.py +938 -0
  200. hyperclaw/unattended.py +60 -0
  201. hyperclaw/updates/__init__.py +54 -0
  202. hyperclaw/updates/check.py +131 -0
  203. hyperclaw/updates/index.py +178 -0
  204. hyperclaw/updates/install.py +110 -0
  205. hyperclaw/updates/managed.py +161 -0
  206. hyperclaw/updates/manifest.py +138 -0
  207. hyperclaw/updates/notices.py +86 -0
  208. hyperclaw/updates/paths.py +53 -0
  209. hyperclaw/updates/release.py +146 -0
  210. hyperclaw/updates/upgrade.py +253 -0
  211. hyperclaw/updates/versioning.py +104 -0
  212. hyperclaw/userconfig.py +119 -0
  213. hyperclaw/xai.py +83 -0
  214. integrations/__init__.py +31 -0
  215. integrations/automation/__init__.py +1 -0
  216. integrations/automation/make.py +87 -0
  217. integrations/automation/zapier.py +87 -0
  218. integrations/base.py +476 -0
  219. integrations/communication/__init__.py +1 -0
  220. integrations/communication/twilio.py +279 -0
  221. integrations/communication/zoom.py +290 -0
  222. integrations/data/__init__.py +1 -0
  223. integrations/data/google_analytics.py +135 -0
  224. integrations/data/postgres.py +182 -0
  225. integrations/data/supabase_connector.py +202 -0
  226. integrations/data/weather_feeds.py +450 -0
  227. integrations/developer/__init__.py +9 -0
  228. integrations/developer/github.py +406 -0
  229. integrations/developer/gitlab.py +298 -0
  230. integrations/enterprise/__init__.py +13 -0
  231. integrations/enterprise/confluence.py +266 -0
  232. integrations/enterprise/hubspot.py +293 -0
  233. integrations/enterprise/jira.py +314 -0
  234. integrations/enterprise/salesforce.py +286 -0
  235. integrations/finance/__init__.py +1 -0
  236. integrations/finance/polymarket_client.py +247 -0
  237. integrations/finance/quickbooks.py +281 -0
  238. integrations/finance/stripe.py +261 -0
  239. integrations/gateway.py +238 -0
  240. integrations/messaging/__init__.py +32 -0
  241. integrations/messaging/discord.py +159 -0
  242. integrations/messaging/email.py +401 -0
  243. integrations/messaging/imessage.py +443 -0
  244. integrations/messaging/signal.py +175 -0
  245. integrations/messaging/slack.py +217 -0
  246. integrations/messaging/sms.py +175 -0
  247. integrations/messaging/teams.py +208 -0
  248. integrations/messaging/telegram.py +260 -0
  249. integrations/messaging/whatsapp.py +203 -0
  250. integrations/productivity/__init__.py +17 -0
  251. integrations/productivity/airtable.py +217 -0
  252. integrations/productivity/asana.py +248 -0
  253. integrations/productivity/google/__init__.py +19 -0
  254. integrations/productivity/google/base.py +89 -0
  255. integrations/productivity/google/calendar.py +272 -0
  256. integrations/productivity/google/docs.py +202 -0
  257. integrations/productivity/google/drive.py +268 -0
  258. integrations/productivity/google/gmail.py +279 -0
  259. integrations/productivity/google/meet.py +218 -0
  260. integrations/productivity/google/sheets.py +222 -0
  261. integrations/productivity/google/tasks.py +199 -0
  262. integrations/productivity/linear.py +279 -0
  263. integrations/productivity/microsoft/__init__.py +13 -0
  264. integrations/productivity/microsoft/base.py +93 -0
  265. integrations/productivity/microsoft/calendar.py +207 -0
  266. integrations/productivity/microsoft/onedrive.py +222 -0
  267. integrations/productivity/microsoft/outlook.py +241 -0
  268. integrations/productivity/microsoft/sharepoint.py +211 -0
  269. integrations/productivity/notion.py +243 -0
  270. integrations/productivity/todoist.py +250 -0
  271. integrations/productivity/trello.py +244 -0
  272. integrations/storage/__init__.py +1 -0
  273. integrations/storage/box.py +279 -0
  274. integrations/storage/dropbox.py +215 -0
  275. integrations/telegram_inbound.py +179 -0
  276. integrations/telegram_poller.py +151 -0
  277. memory/__init__.py +1 -0
  278. memory/causal_graph.py +125 -0
  279. memory/impact_tracker.py +88 -0
  280. memory/vector_memory.py +436 -0
  281. models/__init__.py +12 -0
  282. models/chatjimmy_client.py +210 -0
  283. models/claude_client.py +235 -0
  284. models/claude_code_subagent.py +192 -0
  285. models/openai_client.py +120 -0
  286. models/router.py +162 -0
  287. recursive/discovery_loop.py +118 -0
  288. recursive/optimization_loop.py +135 -0
  289. recursive/validation_loop.py +38 -0
  290. security/__init__.py +1 -0
  291. security/audit_logger.py +106 -0
  292. security/filesystem_guard.py +100 -0
  293. security/hypershield.py +99 -0
  294. security/network_guard.py +97 -0
  295. security/policy_engine.py +198 -0
  296. swarm/__init__.py +1 -0
  297. swarm/agents/__init__.py +0 -0
  298. swarm/agents/base.py +230 -0
  299. swarm/agents/business/__init__.py +0 -0
  300. swarm/agents/business/counsel.py +31 -0
  301. swarm/agents/business/herald.py +32 -0
  302. swarm/agents/business/ledger.py +26 -0
  303. swarm/agents/business/nexus.py +30 -0
  304. swarm/agents/business/ops.py +30 -0
  305. swarm/agents/business/pipeline.py +28 -0
  306. swarm/agents/business/revenue.py +30 -0
  307. swarm/agents/business/sovereign.py +31 -0
  308. swarm/agents/business/strategos.py +27 -0
  309. swarm/agents/business/talent.py +27 -0
  310. swarm/agents/business/venture.py +32 -0
  311. swarm/agents/comms/__init__.py +1 -0
  312. swarm/agents/comms/cipher.py +32 -0
  313. swarm/agents/comms/echo_comms.py +31 -0
  314. swarm/agents/comms/envoy.py +31 -0
  315. swarm/agents/comms/herald_scheduler.py +33 -0
  316. swarm/agents/comms/pulse.py +29 -0
  317. swarm/agents/creative/__init__.py +0 -0
  318. swarm/agents/creative/author.py +27 -0
  319. swarm/agents/creative/lens.py +25 -0
  320. swarm/agents/executive/__init__.py +1 -0
  321. swarm/agents/intelligence/__init__.py +1 -0
  322. swarm/agents/intelligence/arbiter.py +30 -0
  323. swarm/agents/intelligence/sentinel.py +30 -0
  324. swarm/agents/media/__init__.py +0 -0
  325. swarm/agents/personal/__init__.py +0 -0
  326. swarm/agents/personal/atlas.py +27 -0
  327. swarm/agents/personal/hearth.py +26 -0
  328. swarm/agents/personal/midas.py +27 -0
  329. swarm/agents/personal/navigator.py +27 -0
  330. swarm/agents/personal/nourish.py +31 -0
  331. swarm/agents/personal/vitals.py +33 -0
  332. swarm/agents/recursive/__init__.py +0 -0
  333. swarm/agents/recursive/alchemist.py +73 -0
  334. swarm/agents/recursive/calibrator.py +45 -0
  335. swarm/agents/recursive/scout.py +97 -0
  336. swarm/agents/scientific/__init__.py +0 -0
  337. swarm/agents/scientific/cosmos.py +36 -0
  338. swarm/agents/scientific/gaia.py +26 -0
  339. swarm/agents/scientific/medicus.py +33 -0
  340. swarm/agents/scientific/oracle_agent.py +39 -0
  341. swarm/agents/scientific/scribe.py +39 -0
  342. swarm/agents/talent/__init__.py +0 -0
  343. swarm/agents/talent/deal.py +28 -0
  344. swarm/agents/talent/roster.py +29 -0
  345. swarm/agents/talent/scout.py +29 -0
  346. swarm/agents/talent/stage.py +27 -0
  347. swarm/agents/tech/__init__.py +0 -0
  348. swarm/agents/tech/aegis.py +28 -0
  349. swarm/agents/tech/bridge.py +28 -0
  350. swarm/agents/tech/forge.py +28 -0
  351. swarm/agents/trading/__init__.py +1 -0
  352. swarm/agents/trading/global_prediction_engine.py +611 -0
  353. swarm/agents/trading/polymarket_trader.py +165 -0
  354. swarm/agents/trading/prediction_strategist.py +218 -0
  355. swarm/autogen_bridge.py +68 -0
  356. swarm/bid_protocol.py +196 -0
  357. swarm/dispatcher.py +527 -0
  358. swarm/nexus.py +181 -0
  359. swarm/registry.py +112 -0
  360. telegram_listener.py +403 -0
  361. ui/tui/app.py +214 -0
@@ -0,0 +1,224 @@
1
+ """
2
+ Entity and metadata extraction from documents for Civilization Knowledge Layer.
3
+ Uses LLM-based extraction for structured information.
4
+ """
5
+ from __future__ import annotations
6
+ import re
7
+ import logging
8
+ from dataclasses import dataclass, field
9
+ from typing import Any
10
+ from uuid import UUID
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ @dataclass
16
+ class ExtractedEntity:
17
+ entity_type: str # "person", "role", "tool", "system", "process", "metric"
18
+ name: str
19
+ context: str
20
+ confidence: float = 1.0
21
+ metadata: dict = field(default_factory=dict)
22
+
23
+
24
+ @dataclass
25
+ class ExtractedMetadata:
26
+ title: str | None = None
27
+ purpose: str | None = None
28
+ owner: str | None = None
29
+ department: str | None = None
30
+ tags: list[str] = field(default_factory=list)
31
+ roles_mentioned: list[str] = field(default_factory=list)
32
+ tools_mentioned: list[str] = field(default_factory=list)
33
+ systems_mentioned: list[str] = field(default_factory=list)
34
+ metrics_mentioned: list[str] = field(default_factory=list)
35
+ estimated_complexity: str | None = None # "low", "medium", "high"
36
+ document_type: str | None = None
37
+
38
+
39
+ class MetadataExtractor:
40
+ """
41
+ Extracts metadata from document text using pattern matching and heuristics.
42
+ For LLM-based extraction, use with an agent wrapper.
43
+ """
44
+
45
+ # Common role patterns
46
+ ROLE_PATTERNS = [
47
+ r"(?:responsible|owner|lead|manager|coordinator|assigned to|handled by)\s*[:\-]?\s*([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)",
48
+ r"([A-Z][a-z]+\s+(?:Manager|Director|Lead|Coordinator|Specialist|Engineer|Analyst|Administrator))",
49
+ ]
50
+
51
+ # Common tool patterns
52
+ TOOL_PATTERNS = [
53
+ r"(?:using|via|through|in|with)\s+([A-Z][a-zA-Z0-9]+(?:\s+[A-Z][a-zA-Z0-9]+)?)",
54
+ r"(Slack|Jira|Confluence|Notion|Asana|Trello|GitHub|GitLab|Salesforce|HubSpot|Zendesk)",
55
+ ]
56
+
57
+ # System patterns
58
+ SYSTEM_PATTERNS = [
59
+ r"([A-Z][a-zA-Z0-9]+(?:DB|API|Service|System|Platform|Server))",
60
+ r"(?:database|system|service|platform)\s*[:\-]?\s*([A-Z][a-zA-Z0-9]+)",
61
+ ]
62
+
63
+ def extract(self, text: str) -> ExtractedMetadata:
64
+ """Extract metadata from raw document text."""
65
+ metadata = ExtractedMetadata()
66
+
67
+ # Extract title (first line or heading)
68
+ lines = text.strip().split("\n")
69
+ if lines:
70
+ first_line = lines[0].strip()
71
+ # Remove markdown heading markers
72
+ if first_line.startswith("#"):
73
+ first_line = re.sub(r"^#+\s*", "", first_line)
74
+ if len(first_line) < 200:
75
+ metadata.title = first_line
76
+
77
+ # Extract purpose (look for common patterns)
78
+ purpose_match = re.search(
79
+ r"(?:purpose|objective|goal|overview)\s*[:\-]?\s*(.+?)(?:\n|$)",
80
+ text, re.IGNORECASE
81
+ )
82
+ if purpose_match:
83
+ metadata.purpose = purpose_match.group(1).strip()[:500]
84
+
85
+ # Extract roles
86
+ for pattern in self.ROLE_PATTERNS:
87
+ matches = re.findall(pattern, text)
88
+ metadata.roles_mentioned.extend(matches)
89
+ metadata.roles_mentioned = list(set(metadata.roles_mentioned))
90
+
91
+ # Extract tools
92
+ for pattern in self.TOOL_PATTERNS:
93
+ matches = re.findall(pattern, text, re.IGNORECASE)
94
+ metadata.tools_mentioned.extend([m for m in matches if len(m) > 2])
95
+ metadata.tools_mentioned = list(set(metadata.tools_mentioned))
96
+
97
+ # Extract systems
98
+ for pattern in self.SYSTEM_PATTERNS:
99
+ matches = re.findall(pattern, text)
100
+ metadata.systems_mentioned.extend(matches)
101
+ metadata.systems_mentioned = list(set(metadata.systems_mentioned))
102
+
103
+ # Extract tags from hashtags
104
+ hashtags = re.findall(r"#([a-zA-Z][a-zA-Z0-9_-]+)", text)
105
+ metadata.tags = list(set(hashtags))
106
+
107
+ # Estimate complexity based on length and structure
108
+ word_count = len(text.split())
109
+ step_count = len(re.findall(r"(?:step|item|\d+\.)\s", text, re.IGNORECASE))
110
+ if word_count < 300 and step_count < 5:
111
+ metadata.estimated_complexity = "low"
112
+ elif word_count < 1000 and step_count < 15:
113
+ metadata.estimated_complexity = "medium"
114
+ else:
115
+ metadata.estimated_complexity = "high"
116
+
117
+ # Detect document type
118
+ metadata.document_type = self._detect_document_type(text)
119
+
120
+ return metadata
121
+
122
+ def _detect_document_type(self, text: str) -> str | None:
123
+ """Detect the type of document based on content patterns."""
124
+ text_lower = text.lower()
125
+
126
+ if any(kw in text_lower for kw in ["sop", "standard operating procedure", "procedure:"]):
127
+ return "sop"
128
+ if any(kw in text_lower for kw in ["checklist", "□", "☐", "[ ]", "[x]"]):
129
+ return "checklist"
130
+ if any(kw in text_lower for kw in ["runbook", "playbook", "incident", "on-call"]):
131
+ return "runbook"
132
+ if any(kw in text_lower for kw in ["job description", "responsibilities:", "qualifications:"]):
133
+ return "job_description"
134
+ if any(kw in text_lower for kw in ["org chart", "organization chart", "reporting structure"]):
135
+ return "org_chart"
136
+ if any(kw in text_lower for kw in ["workflow", "process flow", "→", "->"]):
137
+ return "workflow"
138
+ if any(kw in text_lower for kw in ["policy", "must comply", "prohibited", "required"]):
139
+ return "policy"
140
+ return "knowledge_article"
141
+
142
+
143
+ class EntityExtractor:
144
+ """
145
+ Extracts named entities and their relationships from text.
146
+ """
147
+
148
+ def __init__(self):
149
+ self.entity_patterns = {
150
+ "person": [
151
+ r"([A-Z][a-z]+\s+[A-Z][a-z]+)", # First Last
152
+ r"@([a-zA-Z][a-zA-Z0-9_]+)", # @username
153
+ ],
154
+ "email": [
155
+ r"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})",
156
+ ],
157
+ "url": [
158
+ r"(https?://[^\s<>\"]+)",
159
+ ],
160
+ "metric": [
161
+ r"(\d+(?:\.\d+)?%)", # Percentages
162
+ r"(\$\d+(?:,\d{3})*(?:\.\d{2})?)", # Currency
163
+ r"(\d+(?:\.\d+)?\s*(?:hours?|mins?|minutes?|days?|weeks?))", # Time durations
164
+ ],
165
+ "date": [
166
+ r"(\d{4}-\d{2}-\d{2})", # ISO date
167
+ r"(\d{1,2}/\d{1,2}/\d{2,4})", # US date
168
+ ],
169
+ }
170
+
171
+ def extract(self, text: str) -> list[ExtractedEntity]:
172
+ """Extract all entities from text."""
173
+ entities = []
174
+
175
+ for entity_type, patterns in self.entity_patterns.items():
176
+ for pattern in patterns:
177
+ for match in re.finditer(pattern, text):
178
+ # Get surrounding context (50 chars before and after)
179
+ start = max(0, match.start() - 50)
180
+ end = min(len(text), match.end() + 50)
181
+ context = text[start:end]
182
+
183
+ entities.append(ExtractedEntity(
184
+ entity_type=entity_type,
185
+ name=match.group(1) if match.lastindex else match.group(0),
186
+ context=context,
187
+ confidence=0.8,
188
+ ))
189
+
190
+ # Deduplicate by name within each type
191
+ seen = set()
192
+ unique_entities = []
193
+ for entity in entities:
194
+ key = (entity.entity_type, entity.name)
195
+ if key not in seen:
196
+ seen.add(key)
197
+ unique_entities.append(entity)
198
+
199
+ return unique_entities
200
+
201
+ def extract_relationships(self, text: str) -> list[dict]:
202
+ """
203
+ Extract relationships between entities.
204
+ Returns list of {"source": str, "target": str, "relationship": str}
205
+ """
206
+ relationships = []
207
+
208
+ # Pattern: "X reports to Y" or "X manages Y"
209
+ report_patterns = [
210
+ (r"([A-Z][a-z]+\s+[A-Z][a-z]+)\s+reports?\s+to\s+([A-Z][a-z]+\s+[A-Z][a-z]+)", "reports_to"),
211
+ (r"([A-Z][a-z]+\s+[A-Z][a-z]+)\s+manages?\s+([A-Z][a-z]+\s+[A-Z][a-z]+)", "manages"),
212
+ (r"([A-Z][a-z]+\s+[A-Z][a-z]+)\s+owns?\s+([A-Z][a-z]+)", "owns"),
213
+ (r"([A-Z][a-z]+\s+[A-Z][a-z]+)\s+is\s+responsible\s+for\s+([A-Z][a-z]+)", "responsible_for"),
214
+ ]
215
+
216
+ for pattern, rel_type in report_patterns:
217
+ for match in re.finditer(pattern, text, re.IGNORECASE):
218
+ relationships.append({
219
+ "source": match.group(1),
220
+ "target": match.group(2),
221
+ "relationship": rel_type,
222
+ })
223
+
224
+ return relationships
@@ -0,0 +1,19 @@
1
+ """
2
+ Interview module for Civilization Knowledge Layer.
3
+ Handles knowledge elicitation through interactive interviews.
4
+ """
5
+ from .interviewer import Interviewer, InterviewSession, InterviewState
6
+ from .gap_detector import GapDetector, KnowledgeGap, GapSeverity
7
+ from .questioner import Questioner, Question, QuestionType
8
+
9
+ __all__ = [
10
+ "Interviewer",
11
+ "InterviewSession",
12
+ "InterviewState",
13
+ "GapDetector",
14
+ "KnowledgeGap",
15
+ "GapSeverity",
16
+ "Questioner",
17
+ "Question",
18
+ "QuestionType",
19
+ ]
@@ -0,0 +1,380 @@
1
+ """
2
+ Gap detector for identifying missing or incomplete knowledge in the Civilization layer.
3
+ Analyzes existing nodes to find coverage gaps, outdated content, and missing relationships.
4
+ """
5
+ from __future__ import annotations
6
+ import logging
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime, timedelta
9
+ from enum import Enum
10
+ from typing import Any
11
+ from uuid import UUID
12
+
13
+ from ..schema import (
14
+ NodeType, NodeStatus, CivilizationNode,
15
+ SOP, Role, Person, Checklist, Runbook, Workflow, OrgChart,
16
+ )
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class GapSeverity(str, Enum):
22
+ LOW = "low"
23
+ MEDIUM = "medium"
24
+ HIGH = "high"
25
+ CRITICAL = "critical"
26
+
27
+
28
+ class GapType(str, Enum):
29
+ MISSING_NODE = "missing_node"
30
+ INCOMPLETE_NODE = "incomplete_node"
31
+ ORPHAN_NODE = "orphan_node"
32
+ MISSING_RELATIONSHIP = "missing_relationship"
33
+ STALE_CONTENT = "stale_content"
34
+ MISSING_OWNER = "missing_owner"
35
+ COVERAGE_GAP = "coverage_gap"
36
+ INCONSISTENCY = "inconsistency"
37
+
38
+
39
+ @dataclass
40
+ class KnowledgeGap:
41
+ """Represents a detected gap in organizational knowledge."""
42
+ id: UUID
43
+ gap_type: GapType
44
+ severity: GapSeverity
45
+ title: str
46
+ description: str
47
+ affected_node_id: UUID | None = None
48
+ affected_node_type: NodeType | None = None
49
+ suggested_action: str | None = None
50
+ detected_at: datetime = field(default_factory=datetime.utcnow)
51
+ resolved_at: datetime | None = None
52
+ metadata: dict = field(default_factory=dict)
53
+
54
+ @property
55
+ def is_resolved(self) -> bool:
56
+ return self.resolved_at is not None
57
+
58
+
59
+ @dataclass
60
+ class GapReport:
61
+ """Summary report of knowledge gaps."""
62
+ org_id: str
63
+ generated_at: datetime = field(default_factory=datetime.utcnow)
64
+ gaps: list[KnowledgeGap] = field(default_factory=list)
65
+ coverage_score: float = 0.0 # 0-100
66
+ health_score: float = 0.0 # 0-100
67
+ recommendations: list[str] = field(default_factory=list)
68
+
69
+ @property
70
+ def critical_count(self) -> int:
71
+ return sum(1 for g in self.gaps if g.severity == GapSeverity.CRITICAL)
72
+
73
+ @property
74
+ def high_count(self) -> int:
75
+ return sum(1 for g in self.gaps if g.severity == GapSeverity.HIGH)
76
+
77
+ def by_type(self, gap_type: GapType) -> list[KnowledgeGap]:
78
+ return [g for g in self.gaps if g.gap_type == gap_type]
79
+
80
+
81
+ class GapDetector:
82
+ """
83
+ Detects gaps and inconsistencies in organizational knowledge.
84
+ Analyzes nodes, relationships, and coverage to identify areas needing attention.
85
+ """
86
+
87
+ # Thresholds for gap detection
88
+ STALE_DAYS_WARNING = 90
89
+ STALE_DAYS_CRITICAL = 180
90
+ MIN_SOP_STEPS = 2
91
+ MIN_CHECKLIST_ITEMS = 2
92
+ MIN_ROLE_RESPONSIBILITIES = 3
93
+
94
+ def __init__(self, org_id: str, store=None):
95
+ self.org_id = org_id
96
+ self.store = store
97
+
98
+ async def analyze(self, nodes: list[CivilizationNode]) -> GapReport:
99
+ """
100
+ Perform comprehensive gap analysis on a set of nodes.
101
+
102
+ Args:
103
+ nodes: List of CivilizationNode instances to analyze
104
+
105
+ Returns:
106
+ GapReport with all detected gaps and recommendations
107
+ """
108
+ from uuid import uuid4
109
+
110
+ report = GapReport(org_id=self.org_id)
111
+ gaps: list[KnowledgeGap] = []
112
+
113
+ # Group nodes by type
114
+ nodes_by_type: dict[NodeType, list[CivilizationNode]] = {}
115
+ for node in nodes:
116
+ nodes_by_type.setdefault(node.node_type, []).append(node)
117
+
118
+ # Run all detection checks
119
+ gaps.extend(self._check_incomplete_nodes(nodes))
120
+ gaps.extend(self._check_orphan_nodes(nodes, nodes_by_type))
121
+ gaps.extend(self._check_stale_content(nodes))
122
+ gaps.extend(self._check_missing_owners(nodes))
123
+ gaps.extend(self._check_coverage_gaps(nodes_by_type))
124
+ gaps.extend(self._check_relationship_gaps(nodes, nodes_by_type))
125
+
126
+ report.gaps = gaps
127
+ report.coverage_score = self._calculate_coverage_score(nodes_by_type)
128
+ report.health_score = self._calculate_health_score(gaps, len(nodes))
129
+ report.recommendations = self._generate_recommendations(gaps, nodes_by_type)
130
+
131
+ return report
132
+
133
+ def _check_incomplete_nodes(self, nodes: list[CivilizationNode]) -> list[KnowledgeGap]:
134
+ """Check for nodes with missing required fields."""
135
+ from uuid import uuid4
136
+ gaps = []
137
+
138
+ for node in nodes:
139
+ issues = []
140
+
141
+ if isinstance(node, SOP):
142
+ if len(node.steps) < self.MIN_SOP_STEPS:
143
+ issues.append("SOP has too few steps")
144
+ if not node.purpose:
145
+ issues.append("SOP missing purpose")
146
+ if not node.roles_involved:
147
+ issues.append("SOP missing roles")
148
+
149
+ elif isinstance(node, Role):
150
+ if len(node.responsibilities) < self.MIN_ROLE_RESPONSIBILITIES:
151
+ issues.append("Role has too few responsibilities")
152
+ if not node.accountabilities:
153
+ issues.append("Role missing accountabilities")
154
+
155
+ elif isinstance(node, Checklist):
156
+ if len(node.items) < self.MIN_CHECKLIST_ITEMS:
157
+ issues.append("Checklist has too few items")
158
+
159
+ elif isinstance(node, Runbook):
160
+ if not node.steps:
161
+ issues.append("Runbook has no steps")
162
+ if not node.escalation_contacts:
163
+ issues.append("Runbook missing escalation contacts")
164
+
165
+ if issues:
166
+ gaps.append(KnowledgeGap(
167
+ id=uuid4(),
168
+ gap_type=GapType.INCOMPLETE_NODE,
169
+ severity=GapSeverity.MEDIUM,
170
+ title=f"Incomplete {node.node_type.value}: {node.title}",
171
+ description="; ".join(issues),
172
+ affected_node_id=node.id,
173
+ affected_node_type=node.node_type,
174
+ suggested_action=f"Review and complete the {node.node_type.value}",
175
+ ))
176
+
177
+ return gaps
178
+
179
+ def _check_orphan_nodes(
180
+ self,
181
+ nodes: list[CivilizationNode],
182
+ nodes_by_type: dict[NodeType, list[CivilizationNode]],
183
+ ) -> list[KnowledgeGap]:
184
+ """Check for nodes with no relationships."""
185
+ from uuid import uuid4
186
+ gaps = []
187
+
188
+ # SOPs without related SOPs or roles
189
+ for sop in nodes_by_type.get(NodeType.SOP, []):
190
+ if isinstance(sop, SOP):
191
+ if not sop.related_sops and not sop.roles_involved:
192
+ gaps.append(KnowledgeGap(
193
+ id=uuid4(),
194
+ gap_type=GapType.ORPHAN_NODE,
195
+ severity=GapSeverity.LOW,
196
+ title=f"Isolated SOP: {sop.title}",
197
+ description="SOP has no related SOPs or roles defined",
198
+ affected_node_id=sop.id,
199
+ affected_node_type=NodeType.SOP,
200
+ suggested_action="Link this SOP to related procedures and roles",
201
+ ))
202
+
203
+ return gaps
204
+
205
+ def _check_stale_content(self, nodes: list[CivilizationNode]) -> list[KnowledgeGap]:
206
+ """Check for outdated content."""
207
+ from uuid import uuid4
208
+ gaps = []
209
+ now = datetime.utcnow()
210
+
211
+ for node in nodes:
212
+ age_days = (now - node.updated_at).days
213
+
214
+ if age_days > self.STALE_DAYS_CRITICAL:
215
+ gaps.append(KnowledgeGap(
216
+ id=uuid4(),
217
+ gap_type=GapType.STALE_CONTENT,
218
+ severity=GapSeverity.HIGH,
219
+ title=f"Critically stale: {node.title}",
220
+ description=f"Not updated in {age_days} days",
221
+ affected_node_id=node.id,
222
+ affected_node_type=node.node_type,
223
+ suggested_action="Review and update this content urgently",
224
+ metadata={"days_since_update": age_days},
225
+ ))
226
+ elif age_days > self.STALE_DAYS_WARNING:
227
+ gaps.append(KnowledgeGap(
228
+ id=uuid4(),
229
+ gap_type=GapType.STALE_CONTENT,
230
+ severity=GapSeverity.MEDIUM,
231
+ title=f"Stale content: {node.title}",
232
+ description=f"Not updated in {age_days} days",
233
+ affected_node_id=node.id,
234
+ affected_node_type=node.node_type,
235
+ suggested_action="Consider reviewing this content",
236
+ metadata={"days_since_update": age_days},
237
+ ))
238
+
239
+ return gaps
240
+
241
+ def _check_missing_owners(self, nodes: list[CivilizationNode]) -> list[KnowledgeGap]:
242
+ """Check for nodes without owners."""
243
+ from uuid import uuid4
244
+ gaps = []
245
+
246
+ for node in nodes:
247
+ if not node.owner_id:
248
+ # SOPs and Runbooks without owners are higher severity
249
+ severity = GapSeverity.HIGH if node.node_type in {NodeType.SOP, NodeType.RUNBOOK} else GapSeverity.LOW
250
+ gaps.append(KnowledgeGap(
251
+ id=uuid4(),
252
+ gap_type=GapType.MISSING_OWNER,
253
+ severity=severity,
254
+ title=f"No owner: {node.title}",
255
+ description=f"{node.node_type.value} has no assigned owner",
256
+ affected_node_id=node.id,
257
+ affected_node_type=node.node_type,
258
+ suggested_action="Assign an owner to this content",
259
+ ))
260
+
261
+ return gaps
262
+
263
+ def _check_coverage_gaps(
264
+ self,
265
+ nodes_by_type: dict[NodeType, list[CivilizationNode]],
266
+ ) -> list[KnowledgeGap]:
267
+ """Check for missing essential node types."""
268
+ from uuid import uuid4
269
+ gaps = []
270
+
271
+ # Essential node types for a well-documented org
272
+ essential_types = {
273
+ NodeType.ORG_CHART: "Organization structure",
274
+ NodeType.ROLE: "Role definitions",
275
+ NodeType.SOP: "Standard operating procedures",
276
+ }
277
+
278
+ for node_type, description in essential_types.items():
279
+ if node_type not in nodes_by_type or len(nodes_by_type[node_type]) == 0:
280
+ gaps.append(KnowledgeGap(
281
+ id=uuid4(),
282
+ gap_type=GapType.COVERAGE_GAP,
283
+ severity=GapSeverity.HIGH,
284
+ title=f"Missing {description}",
285
+ description=f"No {node_type.value} nodes found in knowledge base",
286
+ suggested_action=f"Create {node_type.value} documentation",
287
+ ))
288
+
289
+ return gaps
290
+
291
+ def _check_relationship_gaps(
292
+ self,
293
+ nodes: list[CivilizationNode],
294
+ nodes_by_type: dict[NodeType, list[CivilizationNode]],
295
+ ) -> list[KnowledgeGap]:
296
+ """Check for missing relationships between nodes."""
297
+ from uuid import uuid4
298
+ gaps = []
299
+
300
+ # Check if roles mentioned in SOPs exist
301
+ role_titles = {
302
+ r.role_title.lower() for r in nodes_by_type.get(NodeType.ROLE, [])
303
+ if isinstance(r, Role)
304
+ }
305
+
306
+ for sop in nodes_by_type.get(NodeType.SOP, []):
307
+ if isinstance(sop, SOP):
308
+ for role_name in sop.roles_involved:
309
+ if role_name.lower() not in role_titles:
310
+ gaps.append(KnowledgeGap(
311
+ id=uuid4(),
312
+ gap_type=GapType.MISSING_RELATIONSHIP,
313
+ severity=GapSeverity.MEDIUM,
314
+ title=f"Undefined role: {role_name}",
315
+ description=f"Role '{role_name}' referenced in SOP '{sop.title}' but not defined",
316
+ affected_node_id=sop.id,
317
+ affected_node_type=NodeType.SOP,
318
+ suggested_action=f"Create role definition for '{role_name}'",
319
+ ))
320
+
321
+ return gaps
322
+
323
+ def _calculate_coverage_score(
324
+ self,
325
+ nodes_by_type: dict[NodeType, list[CivilizationNode]],
326
+ ) -> float:
327
+ """Calculate coverage score (0-100) based on node type diversity."""
328
+ essential_types = {NodeType.ORG_CHART, NodeType.ROLE, NodeType.SOP, NodeType.CHECKLIST}
329
+ covered = sum(1 for t in essential_types if t in nodes_by_type and nodes_by_type[t])
330
+ return (covered / len(essential_types)) * 100
331
+
332
+ def _calculate_health_score(self, gaps: list[KnowledgeGap], total_nodes: int) -> float:
333
+ """Calculate health score (0-100) based on gap severity."""
334
+ if total_nodes == 0:
335
+ return 0.0
336
+
337
+ # Weight by severity
338
+ severity_weights = {
339
+ GapSeverity.CRITICAL: 4,
340
+ GapSeverity.HIGH: 2,
341
+ GapSeverity.MEDIUM: 1,
342
+ GapSeverity.LOW: 0.5,
343
+ }
344
+
345
+ total_weight = sum(severity_weights[g.severity] for g in gaps)
346
+ # Higher weight = lower score
347
+ max_weight = total_nodes * 2 # Assume average 2 issues per node is worst case
348
+ score = max(0, 100 - (total_weight / max_weight) * 100)
349
+ return min(100, score)
350
+
351
+ def _generate_recommendations(
352
+ self,
353
+ gaps: list[KnowledgeGap],
354
+ nodes_by_type: dict[NodeType, list[CivilizationNode]],
355
+ ) -> list[str]:
356
+ """Generate prioritized recommendations."""
357
+ recommendations = []
358
+
359
+ # Critical issues first
360
+ critical = [g for g in gaps if g.severity == GapSeverity.CRITICAL]
361
+ if critical:
362
+ recommendations.append(f"Address {len(critical)} critical gaps immediately")
363
+
364
+ # Coverage gaps
365
+ coverage_gaps = [g for g in gaps if g.gap_type == GapType.COVERAGE_GAP]
366
+ if coverage_gaps:
367
+ types = ", ".join(g.title.replace("Missing ", "") for g in coverage_gaps)
368
+ recommendations.append(f"Document missing areas: {types}")
369
+
370
+ # Stale content
371
+ stale = [g for g in gaps if g.gap_type == GapType.STALE_CONTENT]
372
+ if stale:
373
+ recommendations.append(f"Review {len(stale)} pieces of stale content")
374
+
375
+ # Missing owners
376
+ unowned = [g for g in gaps if g.gap_type == GapType.MISSING_OWNER]
377
+ if unowned:
378
+ recommendations.append(f"Assign owners to {len(unowned)} unowned items")
379
+
380
+ return recommendations