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
emdash_core/server.py ADDED
@@ -0,0 +1,153 @@
1
+ """FastAPI server entry point for emdash-core."""
2
+
3
+ import argparse
4
+ import os
5
+ import signal
6
+ import sys
7
+ from contextlib import asynccontextmanager
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ import uvicorn
12
+ from dotenv import load_dotenv
13
+ from fastapi import FastAPI
14
+ from fastapi.middleware.cors import CORSMiddleware
15
+
16
+ from .api import api_router
17
+ from .config import get_config, set_config
18
+
19
+
20
+ def _load_env_files(repo_root: Optional[str] = None):
21
+ """Load .env files from repo root and user config.
22
+
23
+ Uses override=True so .env file takes precedence over shell environment.
24
+ """
25
+ # Also try parent directories to find .env (load first, lower priority)
26
+ current = Path(__file__).parent
27
+ for _ in range(5):
28
+ current = current.parent
29
+ env_path = current / ".env"
30
+ if env_path.exists():
31
+ load_dotenv(env_path, override=True)
32
+ break
33
+
34
+ # Load from repo root if provided (highest priority)
35
+ if repo_root:
36
+ env_path = Path(repo_root) / ".env"
37
+ if env_path.exists():
38
+ load_dotenv(env_path, override=True)
39
+
40
+
41
+ @asynccontextmanager
42
+ async def lifespan(app: FastAPI):
43
+ """Application lifespan handler."""
44
+ config = get_config()
45
+
46
+ # Startup
47
+ print(f"EmDash Core starting on http://{config.host}:{config.port}")
48
+ if config.repo_root:
49
+ print(f"Repository root: {config.repo_root}")
50
+
51
+ # Write port file for CLI/Electron discovery
52
+ port_file = Path.home() / ".emdash" / "server.port"
53
+ port_file.parent.mkdir(parents=True, exist_ok=True)
54
+ port_file.write_text(str(config.port))
55
+
56
+ yield
57
+
58
+ # Shutdown
59
+ print("EmDash Core shutting down...")
60
+ if port_file.exists():
61
+ port_file.unlink()
62
+
63
+
64
+ def create_app(
65
+ repo_root: Optional[str] = None,
66
+ host: str = "127.0.0.1",
67
+ port: int = 8765,
68
+ ) -> FastAPI:
69
+ """Create and configure the FastAPI application."""
70
+ # Load environment variables from .env files
71
+ _load_env_files(repo_root)
72
+
73
+ # Set configuration
74
+ set_config(
75
+ host=host,
76
+ port=port,
77
+ repo_root=repo_root,
78
+ )
79
+
80
+ app = FastAPI(
81
+ title="EmDash Core",
82
+ description="FastAPI server for code intelligence",
83
+ version="0.1.0",
84
+ lifespan=lifespan,
85
+ )
86
+
87
+ # CORS middleware for Electron app
88
+ app.add_middleware(
89
+ CORSMiddleware,
90
+ allow_origins=["*"], # Allow all origins for local development
91
+ allow_credentials=True,
92
+ allow_methods=["*"],
93
+ allow_headers=["*"],
94
+ )
95
+
96
+ # Include API routes
97
+ app.include_router(api_router)
98
+
99
+ return app
100
+
101
+
102
+ def main():
103
+ """Main entry point for the server."""
104
+ parser = argparse.ArgumentParser(description="EmDash Core Server")
105
+ parser.add_argument(
106
+ "--host",
107
+ default="127.0.0.1",
108
+ help="Host to bind to (default: 127.0.0.1)"
109
+ )
110
+ parser.add_argument(
111
+ "--port",
112
+ type=int,
113
+ default=8765,
114
+ help="Port to bind to (default: 8765)"
115
+ )
116
+ parser.add_argument(
117
+ "--repo-root",
118
+ help="Repository root path"
119
+ )
120
+ parser.add_argument(
121
+ "--reload",
122
+ action="store_true",
123
+ help="Enable auto-reload for development"
124
+ )
125
+
126
+ args = parser.parse_args()
127
+
128
+ # Handle graceful shutdown
129
+ def signal_handler(signum, frame):
130
+ print("\nReceived shutdown signal...")
131
+ sys.exit(0)
132
+
133
+ signal.signal(signal.SIGINT, signal_handler)
134
+ signal.signal(signal.SIGTERM, signal_handler)
135
+
136
+ # Create app
137
+ app = create_app(
138
+ repo_root=args.repo_root,
139
+ host=args.host,
140
+ port=args.port,
141
+ )
142
+
143
+ # Run server
144
+ uvicorn.run(
145
+ app,
146
+ host=args.host,
147
+ port=args.port,
148
+ reload=args.reload,
149
+ )
150
+
151
+
152
+ if __name__ == "__main__":
153
+ main()
@@ -0,0 +1,5 @@
1
+ """SSE streaming utilities."""
2
+
3
+ from .stream import SSEHandler, create_sse_stream
4
+
5
+ __all__ = ["SSEHandler", "create_sse_stream"]
@@ -0,0 +1,196 @@
1
+ """SSE streaming infrastructure for agent events."""
2
+
3
+ import asyncio
4
+ import json
5
+ from datetime import datetime
6
+ from enum import Enum
7
+ from typing import Any, AsyncIterator
8
+
9
+ from pydantic import BaseModel
10
+
11
+
12
+ class EventType(str, Enum):
13
+ """Types of events emitted by agents (matches emdash.agent.events.EventType)."""
14
+
15
+ # Tool lifecycle
16
+ TOOL_START = "tool_start"
17
+ TOOL_RESULT = "tool_result"
18
+
19
+ # Agent thinking/progress
20
+ THINKING = "thinking"
21
+ PROGRESS = "progress"
22
+
23
+ # Output
24
+ RESPONSE = "response"
25
+ PARTIAL_RESPONSE = "partial_response"
26
+
27
+ # Interaction
28
+ CLARIFICATION = "clarification"
29
+ CLARIFICATION_RESPONSE = "clarification_response"
30
+
31
+ # Errors
32
+ ERROR = "error"
33
+ WARNING = "warning"
34
+
35
+ # Session
36
+ SESSION_START = "session_start"
37
+ SESSION_END = "session_end"
38
+
39
+ # Context
40
+ CONTEXT_FRAME = "context_frame"
41
+
42
+
43
+ class SSEEvent(BaseModel):
44
+ """An SSE event for streaming to clients."""
45
+
46
+ type: EventType
47
+ data: dict[str, Any]
48
+ timestamp: datetime
49
+ agent_name: str | None = None
50
+
51
+ def to_sse(self) -> str:
52
+ """Format as SSE wire protocol.
53
+
54
+ Returns:
55
+ SSE formatted string: "event: {type}\ndata: {json}\n\n"
56
+ """
57
+ payload = {
58
+ **self.data,
59
+ "timestamp": self.timestamp.isoformat(),
60
+ }
61
+ if self.agent_name:
62
+ payload["agent_name"] = self.agent_name
63
+
64
+ data_json = json.dumps(payload, default=str)
65
+ return f"event: {self.type.value}\ndata: {data_json}\n\n"
66
+
67
+
68
+ class SSEHandler:
69
+ """Event handler that queues events for SSE streaming.
70
+
71
+ This handler implements the EventHandler protocol and collects
72
+ events into an async queue for streaming to HTTP clients.
73
+
74
+ Thread-safe: can be used from both async context and thread pools.
75
+ """
76
+
77
+ def __init__(self, agent_name: str | None = None):
78
+ """Initialize the handler.
79
+
80
+ Args:
81
+ agent_name: Optional name to tag events
82
+ """
83
+ # Use thread-safe queue for cross-thread communication
84
+ import queue
85
+ self._queue: queue.Queue[SSEEvent | None] = queue.Queue()
86
+ self._agent_name = agent_name
87
+ self._closed = False
88
+
89
+ def handle(self, event: Any) -> None:
90
+ """Handle an event from the agent.
91
+
92
+ Args:
93
+ event: AgentEvent from the agent system
94
+ """
95
+ if self._closed:
96
+ return
97
+
98
+ # Convert to SSEEvent
99
+ sse_event = SSEEvent(
100
+ type=EventType(event.type.value),
101
+ data=event.data,
102
+ timestamp=event.timestamp,
103
+ agent_name=event.agent_name or self._agent_name,
104
+ )
105
+
106
+ # Put in queue (thread-safe)
107
+ self._queue.put(sse_event)
108
+
109
+ def emit(self, event_type: EventType, data: dict[str, Any]) -> None:
110
+ """Emit an event directly (thread-safe).
111
+
112
+ Args:
113
+ event_type: Type of event
114
+ data: Event data
115
+ """
116
+ if self._closed:
117
+ return
118
+
119
+ sse_event = SSEEvent(
120
+ type=event_type,
121
+ data=data,
122
+ timestamp=datetime.now(),
123
+ agent_name=self._agent_name,
124
+ )
125
+
126
+ self._queue.put(sse_event)
127
+
128
+ async def emit_async(self, event_type: EventType, data: dict[str, Any]) -> None:
129
+ """Emit an event asynchronously.
130
+
131
+ Args:
132
+ event_type: Type of event
133
+ data: Event data
134
+ """
135
+ if self._closed:
136
+ return
137
+
138
+ sse_event = SSEEvent(
139
+ type=event_type,
140
+ data=data,
141
+ timestamp=datetime.now(),
142
+ agent_name=self._agent_name,
143
+ )
144
+
145
+ self._queue.put(sse_event)
146
+
147
+ def close(self) -> None:
148
+ """Close the handler and signal end of stream."""
149
+ self._closed = True
150
+ self._queue.put(None) # Sentinel to end iteration
151
+
152
+ async def __aiter__(self) -> AsyncIterator[str]:
153
+ """Iterate over SSE-formatted events.
154
+
155
+ Yields:
156
+ SSE formatted strings ready for HTTP streaming
157
+ """
158
+ import queue
159
+
160
+ loop = asyncio.get_event_loop()
161
+ ping_counter = 0
162
+
163
+ while True:
164
+ try:
165
+ # Run blocking queue.get in executor to not block event loop
166
+ try:
167
+ event = await loop.run_in_executor(
168
+ None, # Default executor
169
+ lambda: self._queue.get(timeout=1.0)
170
+ )
171
+ if event is None:
172
+ break
173
+ yield event.to_sse()
174
+ ping_counter = 0
175
+ except queue.Empty:
176
+ ping_counter += 1
177
+ # Send keep-alive ping every 30 seconds
178
+ if ping_counter >= 30:
179
+ yield ": ping\n\n"
180
+ ping_counter = 0
181
+ continue
182
+ except Exception:
183
+ break
184
+
185
+
186
+ async def create_sse_stream(handler: SSEHandler) -> AsyncIterator[str]:
187
+ """Create an SSE stream from a handler.
188
+
189
+ Args:
190
+ handler: SSE handler to stream from
191
+
192
+ Yields:
193
+ SSE formatted event strings
194
+ """
195
+ async for event in handler:
196
+ yield event
@@ -0,0 +1,17 @@
1
+ """Multi-agent swarm execution with git worktrees."""
2
+
3
+ from .task_definition import SwarmTask, SwarmState, TaskStatus
4
+ from .worktree_manager import WorktreeManager, WorktreeInfo, WorktreeError
5
+ from .swarm_runner import SwarmRunner
6
+ from .session_manager import SessionManager
7
+
8
+ __all__ = [
9
+ "SwarmTask",
10
+ "SwarmState",
11
+ "TaskStatus",
12
+ "WorktreeManager",
13
+ "WorktreeInfo",
14
+ "WorktreeError",
15
+ "SwarmRunner",
16
+ "SessionManager",
17
+ ]