emdash-core 0.1.7__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 (187) hide show
  1. emdash_core/__init__.py +3 -0
  2. emdash_core/agent/__init__.py +37 -0
  3. emdash_core/agent/agents.py +225 -0
  4. emdash_core/agent/code_reviewer.py +476 -0
  5. emdash_core/agent/compaction.py +143 -0
  6. emdash_core/agent/context_manager.py +140 -0
  7. emdash_core/agent/events.py +338 -0
  8. emdash_core/agent/handlers.py +224 -0
  9. emdash_core/agent/inprocess_subagent.py +377 -0
  10. emdash_core/agent/mcp/__init__.py +50 -0
  11. emdash_core/agent/mcp/client.py +346 -0
  12. emdash_core/agent/mcp/config.py +302 -0
  13. emdash_core/agent/mcp/manager.py +496 -0
  14. emdash_core/agent/mcp/tool_factory.py +213 -0
  15. emdash_core/agent/prompts/__init__.py +38 -0
  16. emdash_core/agent/prompts/main_agent.py +104 -0
  17. emdash_core/agent/prompts/subagents.py +131 -0
  18. emdash_core/agent/prompts/workflow.py +136 -0
  19. emdash_core/agent/providers/__init__.py +34 -0
  20. emdash_core/agent/providers/base.py +143 -0
  21. emdash_core/agent/providers/factory.py +80 -0
  22. emdash_core/agent/providers/models.py +220 -0
  23. emdash_core/agent/providers/openai_provider.py +463 -0
  24. emdash_core/agent/providers/transformers_provider.py +217 -0
  25. emdash_core/agent/research/__init__.py +81 -0
  26. emdash_core/agent/research/agent.py +143 -0
  27. emdash_core/agent/research/controller.py +254 -0
  28. emdash_core/agent/research/critic.py +428 -0
  29. emdash_core/agent/research/macros.py +469 -0
  30. emdash_core/agent/research/planner.py +449 -0
  31. emdash_core/agent/research/researcher.py +436 -0
  32. emdash_core/agent/research/state.py +523 -0
  33. emdash_core/agent/research/synthesizer.py +594 -0
  34. emdash_core/agent/reviewer_profile.py +475 -0
  35. emdash_core/agent/rules.py +123 -0
  36. emdash_core/agent/runner.py +601 -0
  37. emdash_core/agent/session.py +262 -0
  38. emdash_core/agent/spec_schema.py +66 -0
  39. emdash_core/agent/specification.py +479 -0
  40. emdash_core/agent/subagent.py +397 -0
  41. emdash_core/agent/subagent_prompts.py +13 -0
  42. emdash_core/agent/toolkit.py +482 -0
  43. emdash_core/agent/toolkits/__init__.py +64 -0
  44. emdash_core/agent/toolkits/base.py +96 -0
  45. emdash_core/agent/toolkits/explore.py +47 -0
  46. emdash_core/agent/toolkits/plan.py +55 -0
  47. emdash_core/agent/tools/__init__.py +141 -0
  48. emdash_core/agent/tools/analytics.py +436 -0
  49. emdash_core/agent/tools/base.py +131 -0
  50. emdash_core/agent/tools/coding.py +484 -0
  51. emdash_core/agent/tools/github_mcp.py +592 -0
  52. emdash_core/agent/tools/history.py +13 -0
  53. emdash_core/agent/tools/modes.py +153 -0
  54. emdash_core/agent/tools/plan.py +206 -0
  55. emdash_core/agent/tools/plan_write.py +135 -0
  56. emdash_core/agent/tools/search.py +412 -0
  57. emdash_core/agent/tools/spec.py +341 -0
  58. emdash_core/agent/tools/task.py +262 -0
  59. emdash_core/agent/tools/task_output.py +204 -0
  60. emdash_core/agent/tools/tasks.py +454 -0
  61. emdash_core/agent/tools/traversal.py +588 -0
  62. emdash_core/agent/tools/web.py +179 -0
  63. emdash_core/analytics/__init__.py +5 -0
  64. emdash_core/analytics/engine.py +1286 -0
  65. emdash_core/api/__init__.py +5 -0
  66. emdash_core/api/agent.py +308 -0
  67. emdash_core/api/agents.py +154 -0
  68. emdash_core/api/analyze.py +264 -0
  69. emdash_core/api/auth.py +173 -0
  70. emdash_core/api/context.py +77 -0
  71. emdash_core/api/db.py +121 -0
  72. emdash_core/api/embed.py +131 -0
  73. emdash_core/api/feature.py +143 -0
  74. emdash_core/api/health.py +93 -0
  75. emdash_core/api/index.py +162 -0
  76. emdash_core/api/plan.py +110 -0
  77. emdash_core/api/projectmd.py +210 -0
  78. emdash_core/api/query.py +320 -0
  79. emdash_core/api/research.py +122 -0
  80. emdash_core/api/review.py +161 -0
  81. emdash_core/api/router.py +76 -0
  82. emdash_core/api/rules.py +116 -0
  83. emdash_core/api/search.py +119 -0
  84. emdash_core/api/spec.py +99 -0
  85. emdash_core/api/swarm.py +223 -0
  86. emdash_core/api/tasks.py +109 -0
  87. emdash_core/api/team.py +120 -0
  88. emdash_core/auth/__init__.py +17 -0
  89. emdash_core/auth/github.py +389 -0
  90. emdash_core/config.py +74 -0
  91. emdash_core/context/__init__.py +52 -0
  92. emdash_core/context/models.py +50 -0
  93. emdash_core/context/providers/__init__.py +11 -0
  94. emdash_core/context/providers/base.py +74 -0
  95. emdash_core/context/providers/explored_areas.py +183 -0
  96. emdash_core/context/providers/touched_areas.py +360 -0
  97. emdash_core/context/registry.py +73 -0
  98. emdash_core/context/reranker.py +199 -0
  99. emdash_core/context/service.py +260 -0
  100. emdash_core/context/session.py +352 -0
  101. emdash_core/core/__init__.py +104 -0
  102. emdash_core/core/config.py +454 -0
  103. emdash_core/core/exceptions.py +55 -0
  104. emdash_core/core/models.py +265 -0
  105. emdash_core/core/review_config.py +57 -0
  106. emdash_core/db/__init__.py +67 -0
  107. emdash_core/db/auth.py +134 -0
  108. emdash_core/db/models.py +91 -0
  109. emdash_core/db/provider.py +222 -0
  110. emdash_core/db/providers/__init__.py +5 -0
  111. emdash_core/db/providers/supabase.py +452 -0
  112. emdash_core/embeddings/__init__.py +24 -0
  113. emdash_core/embeddings/indexer.py +534 -0
  114. emdash_core/embeddings/models.py +192 -0
  115. emdash_core/embeddings/providers/__init__.py +7 -0
  116. emdash_core/embeddings/providers/base.py +112 -0
  117. emdash_core/embeddings/providers/fireworks.py +141 -0
  118. emdash_core/embeddings/providers/openai.py +104 -0
  119. emdash_core/embeddings/registry.py +146 -0
  120. emdash_core/embeddings/service.py +215 -0
  121. emdash_core/graph/__init__.py +26 -0
  122. emdash_core/graph/builder.py +134 -0
  123. emdash_core/graph/connection.py +692 -0
  124. emdash_core/graph/schema.py +416 -0
  125. emdash_core/graph/writer.py +667 -0
  126. emdash_core/ingestion/__init__.py +7 -0
  127. emdash_core/ingestion/change_detector.py +150 -0
  128. emdash_core/ingestion/git/__init__.py +5 -0
  129. emdash_core/ingestion/git/commit_analyzer.py +196 -0
  130. emdash_core/ingestion/github/__init__.py +6 -0
  131. emdash_core/ingestion/github/pr_fetcher.py +296 -0
  132. emdash_core/ingestion/github/task_extractor.py +100 -0
  133. emdash_core/ingestion/orchestrator.py +540 -0
  134. emdash_core/ingestion/parsers/__init__.py +10 -0
  135. emdash_core/ingestion/parsers/base_parser.py +66 -0
  136. emdash_core/ingestion/parsers/call_graph_builder.py +121 -0
  137. emdash_core/ingestion/parsers/class_extractor.py +154 -0
  138. emdash_core/ingestion/parsers/function_extractor.py +202 -0
  139. emdash_core/ingestion/parsers/import_analyzer.py +119 -0
  140. emdash_core/ingestion/parsers/python_parser.py +123 -0
  141. emdash_core/ingestion/parsers/registry.py +72 -0
  142. emdash_core/ingestion/parsers/ts_ast_parser.js +313 -0
  143. emdash_core/ingestion/parsers/typescript_parser.py +278 -0
  144. emdash_core/ingestion/repository.py +346 -0
  145. emdash_core/models/__init__.py +38 -0
  146. emdash_core/models/agent.py +68 -0
  147. emdash_core/models/index.py +77 -0
  148. emdash_core/models/query.py +113 -0
  149. emdash_core/planning/__init__.py +7 -0
  150. emdash_core/planning/agent_api.py +413 -0
  151. emdash_core/planning/context_builder.py +265 -0
  152. emdash_core/planning/feature_context.py +232 -0
  153. emdash_core/planning/feature_expander.py +646 -0
  154. emdash_core/planning/llm_explainer.py +198 -0
  155. emdash_core/planning/similarity.py +509 -0
  156. emdash_core/planning/team_focus.py +821 -0
  157. emdash_core/server.py +153 -0
  158. emdash_core/sse/__init__.py +5 -0
  159. emdash_core/sse/stream.py +196 -0
  160. emdash_core/swarm/__init__.py +17 -0
  161. emdash_core/swarm/merge_agent.py +383 -0
  162. emdash_core/swarm/session_manager.py +274 -0
  163. emdash_core/swarm/swarm_runner.py +226 -0
  164. emdash_core/swarm/task_definition.py +137 -0
  165. emdash_core/swarm/worker_spawner.py +319 -0
  166. emdash_core/swarm/worktree_manager.py +278 -0
  167. emdash_core/templates/__init__.py +10 -0
  168. emdash_core/templates/defaults/agent-builder.md.template +82 -0
  169. emdash_core/templates/defaults/focus.md.template +115 -0
  170. emdash_core/templates/defaults/pr-review-enhanced.md.template +309 -0
  171. emdash_core/templates/defaults/pr-review.md.template +80 -0
  172. emdash_core/templates/defaults/project.md.template +85 -0
  173. emdash_core/templates/defaults/research_critic.md.template +112 -0
  174. emdash_core/templates/defaults/research_planner.md.template +85 -0
  175. emdash_core/templates/defaults/research_synthesizer.md.template +128 -0
  176. emdash_core/templates/defaults/reviewer.md.template +81 -0
  177. emdash_core/templates/defaults/spec.md.template +41 -0
  178. emdash_core/templates/defaults/tasks.md.template +78 -0
  179. emdash_core/templates/loader.py +296 -0
  180. emdash_core/utils/__init__.py +45 -0
  181. emdash_core/utils/git.py +84 -0
  182. emdash_core/utils/image.py +502 -0
  183. emdash_core/utils/logger.py +51 -0
  184. emdash_core-0.1.7.dist-info/METADATA +35 -0
  185. emdash_core-0.1.7.dist-info/RECORD +187 -0
  186. emdash_core-0.1.7.dist-info/WHEEL +4 -0
  187. emdash_core-0.1.7.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,264 @@
1
+ """Analytics endpoints."""
2
+
3
+ from typing import Optional
4
+
5
+ from fastapi import APIRouter, HTTPException
6
+ from pydantic import BaseModel, Field
7
+
8
+ router = APIRouter(prefix="/analyze", tags=["analytics"])
9
+
10
+
11
+ class PageRankResult(BaseModel):
12
+ """PageRank result for an entity."""
13
+ qualified_name: str
14
+ name: str
15
+ entity_type: str
16
+ file_path: str
17
+ score: float
18
+
19
+
20
+ class PageRankResponse(BaseModel):
21
+ """PageRank response."""
22
+ results: list[PageRankResult]
23
+ damping: float
24
+
25
+
26
+ class CommunityMember(BaseModel):
27
+ """A member of a community."""
28
+ qualified_name: str
29
+ name: str
30
+ entity_type: str
31
+ file_path: str
32
+
33
+
34
+ class Community(BaseModel):
35
+ """A detected community."""
36
+ id: int
37
+ size: int
38
+ description: Optional[str] = None
39
+ members: list[CommunityMember] = Field(default_factory=list)
40
+ top_files: list[str] = Field(default_factory=list)
41
+
42
+
43
+ class CommunityResponse(BaseModel):
44
+ """Community detection response."""
45
+ communities: list[Community]
46
+ total: int
47
+
48
+
49
+ class AreaImportance(BaseModel):
50
+ """Importance metrics for a directory/file."""
51
+ path: str
52
+ commit_count: int
53
+ author_count: int
54
+ churn: int
55
+ importance_score: float
56
+
57
+
58
+ class AreasResponse(BaseModel):
59
+ """Areas importance response."""
60
+ areas: list[AreaImportance]
61
+
62
+
63
+ class BetweennessResult(BaseModel):
64
+ """Betweenness centrality result."""
65
+ qualified_name: str
66
+ name: str
67
+ entity_type: str
68
+ score: float
69
+
70
+
71
+ def _get_analytics():
72
+ """Get analytics engine."""
73
+ from ..analytics.engine import AnalyticsEngine
74
+ return AnalyticsEngine()
75
+
76
+
77
+ @router.get("/pagerank", response_model=PageRankResponse)
78
+ async def get_pagerank(top: int = 20, damping: float = 0.85):
79
+ """Compute PageRank scores to identify important code.
80
+
81
+ PageRank identifies code that is heavily depended upon.
82
+ """
83
+ try:
84
+ engine = _get_analytics()
85
+ results = engine.compute_pagerank(top=top, damping=damping)
86
+
87
+ return PageRankResponse(
88
+ results=[
89
+ PageRankResult(
90
+ qualified_name=r.get("qualified_name", ""),
91
+ name=r.get("name", ""),
92
+ entity_type=r.get("type", ""),
93
+ file_path=r.get("file_path", ""),
94
+ score=r.get("score", 0.0),
95
+ )
96
+ for r in results
97
+ ],
98
+ damping=damping,
99
+ )
100
+ except Exception as e:
101
+ raise HTTPException(status_code=500, detail=str(e))
102
+
103
+
104
+ @router.get("/communities", response_model=CommunityResponse)
105
+ async def get_communities(
106
+ top: int = 10,
107
+ resolution: float = 1.0,
108
+ include_members: bool = False,
109
+ query: Optional[str] = None,
110
+ ):
111
+ """Detect code communities using Louvain algorithm.
112
+
113
+ Communities are clusters of tightly related code.
114
+
115
+ Args:
116
+ top: Number of top communities to return
117
+ resolution: Louvain resolution parameter
118
+ include_members: Include community members in response
119
+ query: Filter communities by semantic query
120
+ """
121
+ try:
122
+ engine = _get_analytics()
123
+ communities = engine.detect_communities(
124
+ resolution=resolution,
125
+ top=top,
126
+ )
127
+
128
+ result = []
129
+ for c in communities:
130
+ community = Community(
131
+ id=c.get("id", 0),
132
+ size=c.get("size", 0),
133
+ description=c.get("description"),
134
+ top_files=c.get("top_files", []),
135
+ )
136
+ if include_members:
137
+ community.members = [
138
+ CommunityMember(
139
+ qualified_name=m.get("qualified_name", ""),
140
+ name=m.get("name", ""),
141
+ entity_type=m.get("type", ""),
142
+ file_path=m.get("file_path", ""),
143
+ )
144
+ for m in c.get("members", [])
145
+ ]
146
+ result.append(community)
147
+
148
+ return CommunityResponse(
149
+ communities=result,
150
+ total=len(result),
151
+ )
152
+ except Exception as e:
153
+ raise HTTPException(status_code=500, detail=str(e))
154
+
155
+
156
+ @router.get("/communities/{community_id}")
157
+ async def get_community(community_id: int):
158
+ """Get details of a specific community."""
159
+ try:
160
+ engine = _get_analytics()
161
+ community = engine.get_community(community_id)
162
+
163
+ if not community:
164
+ raise HTTPException(status_code=404, detail="Community not found")
165
+
166
+ return Community(
167
+ id=community.get("id", community_id),
168
+ size=community.get("size", 0),
169
+ description=community.get("description"),
170
+ top_files=community.get("top_files", []),
171
+ members=[
172
+ CommunityMember(
173
+ qualified_name=m.get("qualified_name", ""),
174
+ name=m.get("name", ""),
175
+ entity_type=m.get("type", ""),
176
+ file_path=m.get("file_path", ""),
177
+ )
178
+ for m in community.get("members", [])
179
+ ],
180
+ )
181
+ except HTTPException:
182
+ raise
183
+ except Exception as e:
184
+ raise HTTPException(status_code=500, detail=str(e))
185
+
186
+
187
+ @router.get("/areas", response_model=AreasResponse)
188
+ async def get_areas(
189
+ depth: int = 2,
190
+ days: int = 90,
191
+ top: int = 20,
192
+ sort: str = "importance",
193
+ include_files: bool = False,
194
+ ):
195
+ """Get importance metrics by directory/file.
196
+
197
+ Args:
198
+ depth: Directory depth to analyze
199
+ days: Number of days of history to consider
200
+ top: Number of top areas to return
201
+ sort: Sort by: importance, commits, churn, authors
202
+ include_files: Include individual files (not just directories)
203
+ """
204
+ try:
205
+ engine = _get_analytics()
206
+ areas = engine.get_area_importance(
207
+ depth=depth,
208
+ days=days,
209
+ top=top,
210
+ sort_by=sort,
211
+ include_files=include_files,
212
+ )
213
+
214
+ return AreasResponse(
215
+ areas=[
216
+ AreaImportance(
217
+ path=a.get("path", ""),
218
+ commit_count=a.get("commit_count", 0),
219
+ author_count=a.get("author_count", 0),
220
+ churn=a.get("churn", 0),
221
+ importance_score=a.get("importance_score", 0.0),
222
+ )
223
+ for a in areas
224
+ ]
225
+ )
226
+ except Exception as e:
227
+ raise HTTPException(status_code=500, detail=str(e))
228
+
229
+
230
+ @router.get("/betweenness")
231
+ async def get_betweenness(top: int = 20):
232
+ """Compute betweenness centrality.
233
+
234
+ Identifies bridge entities that connect different parts of the codebase.
235
+ """
236
+ try:
237
+ engine = _get_analytics()
238
+ results = engine.compute_betweenness(top=top)
239
+
240
+ return {
241
+ "results": [
242
+ BetweennessResult(
243
+ qualified_name=r.get("qualified_name", ""),
244
+ name=r.get("name", ""),
245
+ entity_type=r.get("type", ""),
246
+ score=r.get("score", 0.0),
247
+ )
248
+ for r in results
249
+ ]
250
+ }
251
+ except Exception as e:
252
+ raise HTTPException(status_code=500, detail=str(e))
253
+
254
+
255
+ @router.get("/commit-importance")
256
+ async def get_commit_importance(top: int = 20):
257
+ """Score files by commit frequency and author diversity."""
258
+ try:
259
+ engine = _get_analytics()
260
+ results = engine.get_commit_importance(top=top)
261
+
262
+ return {"results": results}
263
+ except Exception as e:
264
+ raise HTTPException(status_code=500, detail=str(e))
@@ -0,0 +1,173 @@
1
+ """GitHub OAuth authentication API endpoints."""
2
+
3
+ import asyncio
4
+ from concurrent.futures import ThreadPoolExecutor
5
+ from typing import Optional
6
+
7
+ from fastapi import APIRouter, HTTPException
8
+ from pydantic import BaseModel
9
+
10
+ router = APIRouter(prefix="/auth", tags=["auth"])
11
+
12
+ # Thread pool for blocking operations
13
+ _executor = ThreadPoolExecutor(max_workers=2)
14
+
15
+
16
+ class LoginResponse(BaseModel):
17
+ """Response from login initiation."""
18
+ user_code: str
19
+ verification_uri: str
20
+ expires_in: int
21
+ interval: int
22
+
23
+
24
+ class AuthStatus(BaseModel):
25
+ """Current authentication status."""
26
+ authenticated: bool
27
+ username: Optional[str] = None
28
+ scope: Optional[str] = None
29
+
30
+
31
+ class LoginPollResponse(BaseModel):
32
+ """Response from login polling."""
33
+ status: str # "pending", "success", "expired", "error"
34
+ username: Optional[str] = None
35
+ error: Optional[str] = None
36
+
37
+
38
+ # Store active device flow states
39
+ _device_flows: dict[str, dict] = {}
40
+
41
+
42
+ @router.post("/login", response_model=LoginResponse)
43
+ async def start_login():
44
+ """Start GitHub OAuth device flow.
45
+
46
+ Returns a user code that the user must enter at github.com/login/device.
47
+ The client should then poll /auth/login/poll until authentication completes.
48
+
49
+ Example:
50
+ curl -X POST http://localhost:8765/api/auth/login
51
+ """
52
+ from ..auth.github import GitHubAuth
53
+
54
+ loop = asyncio.get_event_loop()
55
+
56
+ def _request_device_code():
57
+ auth = GitHubAuth()
58
+ return auth.request_device_code()
59
+
60
+ try:
61
+ response = await loop.run_in_executor(_executor, _request_device_code)
62
+
63
+ # Store device code for polling
64
+ _device_flows[response.user_code] = {
65
+ "device_code": response.device_code,
66
+ "interval": response.interval,
67
+ "expires_in": response.expires_in,
68
+ }
69
+
70
+ return LoginResponse(
71
+ user_code=response.user_code,
72
+ verification_uri=response.verification_uri,
73
+ expires_in=response.expires_in,
74
+ interval=response.interval,
75
+ )
76
+ except Exception as e:
77
+ raise HTTPException(status_code=500, detail=str(e))
78
+
79
+
80
+ @router.post("/login/poll/{user_code}", response_model=LoginPollResponse)
81
+ async def poll_login(user_code: str):
82
+ """Poll for login completion.
83
+
84
+ After starting login, the client should poll this endpoint
85
+ every `interval` seconds until status is "success" or "expired".
86
+
87
+ Args:
88
+ user_code: The user code returned from /auth/login
89
+
90
+ Example:
91
+ curl -X POST http://localhost:8765/api/auth/login/poll/ABCD-1234
92
+ """
93
+ from ..auth.github import GitHubAuth
94
+
95
+ if user_code not in _device_flows:
96
+ raise HTTPException(status_code=404, detail="Device flow not found")
97
+
98
+ flow = _device_flows[user_code]
99
+ device_code = flow["device_code"]
100
+
101
+ loop = asyncio.get_event_loop()
102
+
103
+ def _poll_for_token():
104
+ auth = GitHubAuth()
105
+ return auth.poll_for_token(device_code)
106
+
107
+ try:
108
+ result = await loop.run_in_executor(_executor, _poll_for_token)
109
+
110
+ if result is None:
111
+ # Still pending
112
+ return LoginPollResponse(status="pending")
113
+
114
+ if isinstance(result, str):
115
+ # Error message
116
+ if "expired" in result.lower():
117
+ del _device_flows[user_code]
118
+ return LoginPollResponse(status="expired", error=result)
119
+ return LoginPollResponse(status="error", error=result)
120
+
121
+ # Success - result is AuthConfig
122
+ del _device_flows[user_code]
123
+ return LoginPollResponse(
124
+ status="success",
125
+ username=result.username,
126
+ )
127
+
128
+ except Exception as e:
129
+ return LoginPollResponse(status="error", error=str(e))
130
+
131
+
132
+ @router.post("/logout")
133
+ async def logout():
134
+ """Sign out by removing stored credentials.
135
+
136
+ Example:
137
+ curl -X POST http://localhost:8765/api/auth/logout
138
+ """
139
+ from ..auth.github import GitHubAuth
140
+
141
+ loop = asyncio.get_event_loop()
142
+
143
+ def _clear_auth():
144
+ auth = GitHubAuth()
145
+ auth.clear_auth()
146
+
147
+ await loop.run_in_executor(_executor, _clear_auth)
148
+ return {"success": True, "message": "Logged out successfully"}
149
+
150
+
151
+ @router.get("/status", response_model=AuthStatus)
152
+ async def get_status():
153
+ """Get current authentication status.
154
+
155
+ Example:
156
+ curl http://localhost:8765/api/auth/status
157
+ """
158
+ from ..auth.github import get_auth_status
159
+
160
+ loop = asyncio.get_event_loop()
161
+
162
+ def _get_status():
163
+ return get_auth_status()
164
+
165
+ try:
166
+ status = await loop.run_in_executor(_executor, _get_status)
167
+ return AuthStatus(
168
+ authenticated=status.get("authenticated", False),
169
+ username=status.get("username"),
170
+ scope=status.get("scope"),
171
+ )
172
+ except Exception:
173
+ return AuthStatus(authenticated=False)
@@ -0,0 +1,77 @@
1
+ """Session context endpoints."""
2
+
3
+ from typing import Optional
4
+
5
+ from fastapi import APIRouter
6
+ from pydantic import BaseModel, Field
7
+
8
+ router = APIRouter(prefix="/context", tags=["context"])
9
+
10
+
11
+ class ContextEntity(BaseModel):
12
+ """An entity in the context."""
13
+ qualified_name: str
14
+ entity_type: str
15
+ file_path: str
16
+ relevance: float
17
+
18
+
19
+ class SessionContext(BaseModel):
20
+ """Current session context."""
21
+ entities: list[ContextEntity] = Field(default_factory=list)
22
+ files: list[str] = Field(default_factory=list)
23
+ summary: Optional[str] = None
24
+
25
+
26
+ @router.get("", response_model=SessionContext)
27
+ async def get_context():
28
+ """Get current session context."""
29
+ try:
30
+ from ..context.service import ContextService
31
+
32
+ service = ContextService()
33
+ ctx = service.get_context()
34
+
35
+ return SessionContext(
36
+ entities=[
37
+ ContextEntity(
38
+ qualified_name=e.get("qualified_name", ""),
39
+ entity_type=e.get("type", ""),
40
+ file_path=e.get("file_path", ""),
41
+ relevance=e.get("relevance", 0.0),
42
+ )
43
+ for e in ctx.get("entities", [])
44
+ ],
45
+ files=ctx.get("files", []),
46
+ summary=ctx.get("summary"),
47
+ )
48
+ except Exception:
49
+ return SessionContext()
50
+
51
+
52
+ @router.delete("")
53
+ async def clear_context():
54
+ """Clear session context."""
55
+ try:
56
+ from ..context.service import ContextService
57
+
58
+ service = ContextService()
59
+ service.clear()
60
+
61
+ return {"success": True}
62
+ except Exception as e:
63
+ return {"success": False, "error": str(e)}
64
+
65
+
66
+ @router.get("/prompt")
67
+ async def get_context_prompt():
68
+ """Get context as a prompt for system prompt injection."""
69
+ try:
70
+ from ..context.service import ContextService
71
+
72
+ service = ContextService()
73
+ prompt = service.get_prompt()
74
+
75
+ return {"prompt": prompt}
76
+ except Exception:
77
+ return {"prompt": ""}
emdash_core/api/db.py ADDED
@@ -0,0 +1,121 @@
1
+ """Database management endpoints."""
2
+
3
+ from fastapi import APIRouter, HTTPException
4
+ from pydantic import BaseModel
5
+
6
+ router = APIRouter(prefix="/db", tags=["database"])
7
+
8
+
9
+ class DbStats(BaseModel):
10
+ """Database statistics."""
11
+ node_count: int
12
+ relationship_count: int
13
+ file_count: int
14
+ function_count: int
15
+ class_count: int
16
+ community_count: int
17
+
18
+
19
+ class DbInitResponse(BaseModel):
20
+ """Response from database initialization."""
21
+ success: bool
22
+ message: str
23
+
24
+
25
+ class DbTestResponse(BaseModel):
26
+ """Response from database test."""
27
+ connected: bool
28
+ database_path: str
29
+ message: str
30
+
31
+
32
+ def _get_connection():
33
+ """Get database connection."""
34
+ from ..graph.connection import KuzuConnection
35
+ return KuzuConnection()
36
+
37
+
38
+ @router.post("/init", response_model=DbInitResponse)
39
+ async def db_init():
40
+ """Initialize the Kuzu database schema.
41
+
42
+ Creates all node and relationship tables required for the knowledge graph.
43
+ """
44
+ try:
45
+ conn = _get_connection()
46
+ from ..graph.schema import SchemaManager
47
+
48
+ schema = SchemaManager(conn)
49
+ schema.initialize_schema()
50
+
51
+ return DbInitResponse(
52
+ success=True,
53
+ message="Database schema initialized successfully"
54
+ )
55
+ except Exception as e:
56
+ raise HTTPException(status_code=500, detail=str(e))
57
+
58
+
59
+ @router.post("/clear", response_model=DbInitResponse)
60
+ async def db_clear(confirm: bool = False):
61
+ """Clear all data from the database.
62
+
63
+ Args:
64
+ confirm: Must be True to proceed with clearing
65
+ """
66
+ if not confirm:
67
+ raise HTTPException(
68
+ status_code=400,
69
+ detail="Must set confirm=true to clear database"
70
+ )
71
+
72
+ try:
73
+ conn = _get_connection()
74
+ conn.clear_database()
75
+
76
+ return DbInitResponse(
77
+ success=True,
78
+ message="Database cleared successfully"
79
+ )
80
+ except Exception as e:
81
+ raise HTTPException(status_code=500, detail=str(e))
82
+
83
+
84
+ @router.get("/stats", response_model=DbStats)
85
+ async def db_stats():
86
+ """Get database statistics."""
87
+ try:
88
+ conn = _get_connection()
89
+ info = conn.get_database_info()
90
+
91
+ return DbStats(
92
+ node_count=info.get("node_count", 0),
93
+ relationship_count=info.get("relationship_count", 0),
94
+ file_count=info.get("file_count", 0),
95
+ function_count=info.get("function_count", 0),
96
+ class_count=info.get("class_count", 0),
97
+ community_count=info.get("community_count", 0),
98
+ )
99
+ except Exception as e:
100
+ raise HTTPException(status_code=500, detail=str(e))
101
+
102
+
103
+ @router.get("/test", response_model=DbTestResponse)
104
+ async def db_test():
105
+ """Test database connection."""
106
+ try:
107
+ conn = _get_connection()
108
+ # Try to connect
109
+ conn.connect()
110
+
111
+ return DbTestResponse(
112
+ connected=True,
113
+ database_path=str(conn.database_path),
114
+ message="Database connection successful"
115
+ )
116
+ except Exception as e:
117
+ return DbTestResponse(
118
+ connected=False,
119
+ database_path="",
120
+ message=str(e)
121
+ )