emdash-core 0.1.37__py3-none-any.whl → 0.1.60__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 (60) hide show
  1. emdash_core/agent/agents.py +9 -0
  2. emdash_core/agent/background.py +481 -0
  3. emdash_core/agent/inprocess_subagent.py +70 -1
  4. emdash_core/agent/mcp/config.py +78 -2
  5. emdash_core/agent/prompts/main_agent.py +53 -1
  6. emdash_core/agent/prompts/plan_mode.py +65 -44
  7. emdash_core/agent/prompts/subagents.py +73 -1
  8. emdash_core/agent/prompts/workflow.py +179 -28
  9. emdash_core/agent/providers/models.py +1 -1
  10. emdash_core/agent/providers/openai_provider.py +10 -0
  11. emdash_core/agent/research/researcher.py +154 -45
  12. emdash_core/agent/runner/agent_runner.py +145 -19
  13. emdash_core/agent/runner/sdk_runner.py +29 -2
  14. emdash_core/agent/skills.py +81 -1
  15. emdash_core/agent/toolkit.py +87 -11
  16. emdash_core/agent/tools/__init__.py +2 -0
  17. emdash_core/agent/tools/coding.py +344 -52
  18. emdash_core/agent/tools/lsp.py +361 -0
  19. emdash_core/agent/tools/skill.py +21 -1
  20. emdash_core/agent/tools/task.py +16 -19
  21. emdash_core/agent/tools/task_output.py +262 -32
  22. emdash_core/agent/verifier/__init__.py +11 -0
  23. emdash_core/agent/verifier/manager.py +295 -0
  24. emdash_core/agent/verifier/models.py +97 -0
  25. emdash_core/{swarm/worktree_manager.py → agent/worktree.py} +19 -1
  26. emdash_core/api/agent.py +297 -2
  27. emdash_core/api/research.py +3 -3
  28. emdash_core/api/router.py +0 -4
  29. emdash_core/context/longevity.py +197 -0
  30. emdash_core/context/providers/explored_areas.py +83 -39
  31. emdash_core/context/reranker.py +35 -144
  32. emdash_core/context/simple_reranker.py +500 -0
  33. emdash_core/context/tool_relevance.py +84 -0
  34. emdash_core/core/config.py +8 -0
  35. emdash_core/graph/__init__.py +8 -1
  36. emdash_core/graph/connection.py +24 -3
  37. emdash_core/graph/writer.py +7 -1
  38. emdash_core/models/agent.py +10 -0
  39. emdash_core/server.py +1 -6
  40. emdash_core/sse/stream.py +16 -1
  41. emdash_core/utils/__init__.py +0 -2
  42. emdash_core/utils/git.py +103 -0
  43. emdash_core/utils/image.py +147 -160
  44. {emdash_core-0.1.37.dist-info → emdash_core-0.1.60.dist-info}/METADATA +6 -6
  45. {emdash_core-0.1.37.dist-info → emdash_core-0.1.60.dist-info}/RECORD +47 -52
  46. emdash_core/api/swarm.py +0 -223
  47. emdash_core/db/__init__.py +0 -67
  48. emdash_core/db/auth.py +0 -134
  49. emdash_core/db/models.py +0 -91
  50. emdash_core/db/provider.py +0 -222
  51. emdash_core/db/providers/__init__.py +0 -5
  52. emdash_core/db/providers/supabase.py +0 -452
  53. emdash_core/swarm/__init__.py +0 -17
  54. emdash_core/swarm/merge_agent.py +0 -383
  55. emdash_core/swarm/session_manager.py +0 -274
  56. emdash_core/swarm/swarm_runner.py +0 -226
  57. emdash_core/swarm/task_definition.py +0 -137
  58. emdash_core/swarm/worker_spawner.py +0 -319
  59. {emdash_core-0.1.37.dist-info → emdash_core-0.1.60.dist-info}/WHEEL +0 -0
  60. {emdash_core-0.1.37.dist-info → emdash_core-0.1.60.dist-info}/entry_points.txt +0 -0
@@ -65,6 +65,14 @@ class MCPServerConfig:
65
65
  # Default to .emdash/index/kuzu_db in cwd
66
66
  default_path = Path.cwd() / ".emdash" / "index" / "kuzu_db"
67
67
  return str(default_path)
68
+ # Check for cclsp config path - use emdash config default
69
+ if var_name == "CCLSP_CONFIG_PATH":
70
+ env_val = os.getenv(var_name)
71
+ if env_val:
72
+ return env_val
73
+ # Default to .emdash/cclsp.json in cwd
74
+ default_path = Path.cwd() / ".emdash" / "cclsp.json"
75
+ return str(default_path)
68
76
  # Fall back to environment variable
69
77
  return os.getenv(var_name, "")
70
78
 
@@ -244,6 +252,8 @@ def get_default_mcp_servers() -> dict[str, MCPServerConfig]:
244
252
  """
245
253
  # Check if graph MCP is enabled via env flag
246
254
  enable_graph_mcp = os.getenv("ENABLE_GRAPH_MCP", "false").lower() == "true"
255
+ # Check if LSP is enabled via env flag (disabled by default)
256
+ enable_cclsp = os.getenv("USE_LSP", "false").lower() == "true"
247
257
 
248
258
  return {
249
259
  "github": MCPServerConfig(
@@ -266,6 +276,16 @@ def get_default_mcp_servers() -> dict[str, MCPServerConfig]:
266
276
  enabled=enable_graph_mcp,
267
277
  timeout=30,
268
278
  ),
279
+ "cclsp": MCPServerConfig(
280
+ name="cclsp",
281
+ command="npx",
282
+ args=["cclsp@latest"],
283
+ env={
284
+ "CCLSP_CONFIG_PATH": "${CCLSP_CONFIG_PATH}",
285
+ },
286
+ enabled=enable_cclsp, # Disabled by default, enable with USE_LSP=true
287
+ timeout=60, # LSP operations can take longer
288
+ ),
269
289
  }
270
290
 
271
291
 
@@ -297,6 +317,62 @@ def ensure_mcp_config(path: Path) -> MCPConfigFile:
297
317
  MCPConfigFile (loaded or newly created)
298
318
  """
299
319
  if path.exists():
300
- return MCPConfigFile.load(path)
320
+ config = MCPConfigFile.load(path)
301
321
  else:
302
- return create_default_mcp_config(path)
322
+ config = create_default_mcp_config(path)
323
+
324
+ # Ensure cclsp.json config exists if cclsp is enabled
325
+ if config.servers.get("cclsp") and config.servers["cclsp"].enabled:
326
+ cclsp_config_path = path.parent / "cclsp.json"
327
+ ensure_cclsp_config(cclsp_config_path)
328
+
329
+ return config
330
+
331
+
332
+ def get_default_cclsp_config() -> dict:
333
+ """Get the default cclsp configuration for TypeScript/React projects.
334
+
335
+ Returns:
336
+ Dictionary with cclsp configuration
337
+ """
338
+ return {
339
+ "servers": [
340
+ {
341
+ "extensions": ["ts", "tsx", "js", "jsx"],
342
+ "command": ["npx", "typescript-language-server", "--stdio"],
343
+ "rootDir": ".",
344
+ },
345
+ {
346
+ "extensions": ["py", "pyi"],
347
+ "command": ["pylsp"],
348
+ "rootDir": ".",
349
+ },
350
+ ]
351
+ }
352
+
353
+
354
+ def create_cclsp_config(path: Path) -> None:
355
+ """Create a default cclsp configuration file.
356
+
357
+ Args:
358
+ path: Path to save the cclsp.json file
359
+ """
360
+ config = get_default_cclsp_config()
361
+
362
+ # Ensure directory exists
363
+ path.parent.mkdir(parents=True, exist_ok=True)
364
+
365
+ with open(path, "w") as f:
366
+ json.dump(config, f, indent=2)
367
+
368
+ log.info(f"Created default cclsp config at {path}")
369
+
370
+
371
+ def ensure_cclsp_config(path: Path) -> None:
372
+ """Ensure cclsp config exists, creating default if needed.
373
+
374
+ Args:
375
+ path: Path to the cclsp.json file
376
+ """
377
+ if not path.exists():
378
+ create_cclsp_config(path)
@@ -10,6 +10,8 @@ from .workflow import (
10
10
  OUTPUT_GUIDELINES,
11
11
  PARALLEL_EXECUTION,
12
12
  TODO_LIST_GUIDANCE,
13
+ VERIFICATION_AND_CRITIQUE,
14
+ ACTION_NOT_ANNOUNCEMENT,
13
15
  )
14
16
 
15
17
  # Base system prompt template with placeholder for tools
@@ -20,7 +22,7 @@ _BASE_PROMPT = """You are a code exploration and implementation assistant. You o
20
22
 
21
23
  # Main agent system prompt - same for both code and plan modes
22
24
  # Main agent is always an orchestrator that delegates to subagents
23
- BASE_SYSTEM_PROMPT = _BASE_PROMPT + WORKFLOW_PATTERNS + PARALLEL_EXECUTION + EXPLORATION_STRATEGY + TODO_LIST_GUIDANCE + OUTPUT_GUIDELINES
25
+ BASE_SYSTEM_PROMPT = _BASE_PROMPT + WORKFLOW_PATTERNS + PARALLEL_EXECUTION + EXPLORATION_STRATEGY + TODO_LIST_GUIDANCE + ACTION_NOT_ANNOUNCEMENT + VERIFICATION_AND_CRITIQUE + OUTPUT_GUIDELINES
24
26
 
25
27
  # Legacy aliases
26
28
  CODE_MODE_PROMPT = BASE_SYSTEM_PROMPT
@@ -40,10 +42,15 @@ def build_system_prompt(toolkit) -> str:
40
42
  agents_section = build_agents_section(toolkit)
41
43
  skills_section = build_skills_section()
42
44
  rules_section = build_rules_section()
45
+ session_section = build_session_context_section(toolkit)
43
46
 
44
47
  # Main agent always uses the same prompt - it orchestrates and delegates
45
48
  prompt = BASE_SYSTEM_PROMPT.format(tools_section=tools_section)
46
49
 
50
+ # Add session context section first (repo, branch, status)
51
+ if session_section:
52
+ prompt += "\n" + session_section
53
+
47
54
  # Add agents section so main agent knows what agents are available
48
55
  if agents_section:
49
56
  prompt += "\n" + agents_section
@@ -59,6 +66,51 @@ def build_system_prompt(toolkit) -> str:
59
66
  return prompt
60
67
 
61
68
 
69
+ def build_session_context_section(toolkit) -> str:
70
+ """Build the session context section with repo, branch, and git status.
71
+
72
+ Args:
73
+ toolkit: The agent toolkit (to access repo_root)
74
+
75
+ Returns:
76
+ Formatted string with session context, or empty string if not in a git repo
77
+ """
78
+ from ...utils.git import (
79
+ get_repo_name,
80
+ get_current_branch,
81
+ get_git_status_summary,
82
+ )
83
+
84
+ repo_root = getattr(toolkit, '_repo_root', None)
85
+ if not repo_root:
86
+ return ""
87
+
88
+ repo_name = get_repo_name(repo_root)
89
+ branch = get_current_branch(repo_root)
90
+ status = get_git_status_summary(repo_root)
91
+
92
+ # Only include if we have at least some git info
93
+ if not any([repo_name, branch, status]):
94
+ return ""
95
+
96
+ lines = [
97
+ "## Session Context",
98
+ "",
99
+ ]
100
+
101
+ if repo_name:
102
+ lines.append(f"- **Repository**: {repo_name}")
103
+ if branch:
104
+ lines.append(f"- **Branch**: {branch}")
105
+ if status:
106
+ lines.append(f"- **Git Status**: {status}")
107
+
108
+ lines.append(f"- **Working Directory**: {repo_root}")
109
+ lines.append("")
110
+
111
+ return "\n".join(lines)
112
+
113
+
62
114
  def build_rules_section() -> str:
63
115
  """Build the rules section of the system prompt.
64
116
 
@@ -62,21 +62,45 @@ Even for new features, explore first to understand:
62
62
  - Where similar features exist
63
63
  - What conventions to follow
64
64
 
65
- Use these tools directly:
65
+ **Direct Tools (for targeted queries):**
66
66
  - `glob` - Find files by pattern (e.g., `glob(pattern="**/*.py")`)
67
67
  - `grep` - Search file contents (e.g., `grep(pattern="class User", path="src/")`)
68
68
  - `read_file` - Read specific files
69
69
  - `semantic_search` - Find conceptually related code
70
70
 
71
- For deep parallel exploration, you can spawn Explore agents:
71
+ **Parallel Explore Agents (for complex tasks):**
72
+ For non-trivial tasks, launch up to 3 Explore agents IN PARALLEL to speed up exploration:
73
+
72
74
  ```python
75
+ # Launch multiple agents in a SINGLE response for parallel execution
73
76
  task(
74
77
  subagent_type="Explore",
75
78
  prompt="Find all authentication-related files and patterns",
76
79
  description="Explore auth patterns"
77
80
  )
81
+ task(
82
+ subagent_type="Explore",
83
+ prompt="Find all API endpoint definitions and routing",
84
+ description="Explore API routes"
85
+ )
86
+ task(
87
+ subagent_type="Explore",
88
+ prompt="Find all database models and schemas",
89
+ description="Explore data models"
90
+ )
78
91
  ```
79
92
 
93
+ **When to use parallel agents:**
94
+ - Task touches multiple subsystems (auth, API, database, etc.)
95
+ - Codebase is large or unfamiliar
96
+ - Requirements span multiple domains
97
+ - You need to understand architectural patterns quickly
98
+
99
+ **When to use direct tools:**
100
+ - Simple, targeted queries ("find the User class")
101
+ - You already know where to look
102
+ - Quick file reads or pattern matches
103
+
80
104
  ### Phase 2: CLARIFY (Only After Exploring)
81
105
  If requirements are still unclear AFTER exploration, ask focused questions.
82
106
 
@@ -94,6 +118,27 @@ Consider:
94
118
  - Edge cases and error handling
95
119
  - Verification/testing strategy
96
120
 
121
+ **For complex architectural decisions**, you can launch a Plan agent:
122
+
123
+ ```python
124
+ task(
125
+ subagent_type="Plan",
126
+ prompt="Design the authentication system architecture considering: existing patterns found in auth/, session management in services/session.py, and the need for OAuth2 support",
127
+ description="Design auth architecture"
128
+ )
129
+ ```
130
+
131
+ **When to use Plan agents:**
132
+ - Multiple valid architectural approaches exist
133
+ - Trade-offs need careful analysis
134
+ - Design impacts multiple subsystems
135
+ - You need expert-level design recommendations
136
+
137
+ **When to synthesize directly:**
138
+ - Implementation is straightforward
139
+ - Clear patterns exist to follow
140
+ - Single file or localized changes
141
+
97
142
  ### Phase 4: REVIEW
98
143
  Before finalizing, verify the plan is complete and actionable.
99
144
 
@@ -124,52 +169,27 @@ exit_plan()
124
169
 
125
170
  ## PLAN FILE FORMAT
126
171
 
127
- Your plan must be written to `{plan_file_path}` and include:
172
+ Your plan must be written to `{plan_file_path}`. Use **compact formatting** - no blank lines between headers and content:
128
173
 
129
174
  ```markdown
130
- # Implementation Plan: <Title>
131
-
175
+ # <Title>
132
176
  ## Summary
133
- <1-2 sentence overview of what will be implemented>
134
-
177
+ <1-2 sentence overview>
135
178
  ## Approach
136
- <High-level strategy - describe WHAT changes, not HOW (no code)>
137
-
138
- ### For Bug Fixes:
139
- - Root cause analysis
140
- - Fix location and strategy
141
- - Regression prevention
142
-
143
- ### For New Features:
144
- - Architecture decisions
145
- - Component breakdown
146
- - Integration points
147
-
148
- ### For Refactors:
149
- - Current state problems
150
- - Target state benefits
151
- - Migration strategy
152
-
153
- ## Implementation Steps
179
+ <High-level strategy - WHAT changes, not HOW>
180
+ - For bugs: root cause, fix location, regression prevention
181
+ - For features: architecture, components, integration points
182
+ - For refactors: current problems, target benefits, migration
183
+ ## Steps
154
184
  1. <Step 1 - what to do, which file>
155
185
  2. <Step 2 - what to do, which file>
156
- ...
157
-
158
186
  ## Critical Files
159
- List the 3-5 most important files with brief justification:
160
-
161
187
  | File | Purpose |
162
188
  |------|---------|
163
- | `path/to/file1.py` | <why this file is critical> |
164
- | `path/to/file2.py` | <why this file is critical> |
165
- ...
166
-
189
+ | `path/to/file.py` | <why critical> |
167
190
  ## Verification
168
- - [ ] <Specific test or check to verify correctness>
169
- - [ ] <Another verification step>
170
- ...
171
-
172
- ## Risks & Considerations
191
+ - [ ] <Test or check to verify>
192
+ ## Risks
173
193
  - <Potential issue and mitigation>
174
194
  ```
175
195
 
@@ -179,8 +199,8 @@ List the 3-5 most important files with brief justification:
179
199
 
180
200
  ```
181
201
  ┌─────────────────┐
182
- │ 1. EXPLORE │ ◄─── Use tools directly: glob, grep, read_file
183
- (your tools)
202
+ │ 1. EXPLORE │ ◄─── Direct tools OR up to 3 Explore agents in parallel
203
+ (tools/agents)
184
204
  └────────┬────────┘
185
205
  │ Codebase understood
186
206
 
@@ -191,8 +211,8 @@ List the 3-5 most important files with brief justification:
191
211
  │ Requirements clear
192
212
 
193
213
  ┌─────────────────┐
194
- │ 3. DESIGN │ ◄─── Synthesize findings into approach
195
- (your analysis) │
214
+ │ 3. DESIGN │ ◄─── Direct synthesis OR Plan agent for complex decisions
215
+ │(analysis/agent) │
196
216
  └────────┬────────┘
197
217
  │ Design complete
198
218
 
@@ -247,8 +267,9 @@ Only call `exit_plan` when ALL of these are true:
247
267
  ## CORRECT BEHAVIOR
248
268
 
249
269
  - Use `ask_followup_question` for clarifying requirements
250
- - Use your tools directly (glob, grep, read_file) for exploration
251
- - Use `task(subagent_type="Explore", ...)` only for deep parallel exploration
270
+ - Use your tools directly (glob, grep, read_file) for simple, targeted exploration
271
+ - Use `task(subagent_type="Explore", ...)` for complex tasks - launch up to 3 in parallel
272
+ - Use `task(subagent_type="Plan", ...)` for complex architectural decisions
252
273
  - Write plan to `{plan_file_path}` before exiting
253
274
  - Use `exit_plan` to request plan approval
254
275
  - Focus on WHAT to change, not HOW (no code snippets)
@@ -198,6 +198,8 @@ def get_subagent_prompt(subagent_type: str, repo_root=None) -> str:
198
198
  Raises:
199
199
  ValueError: If agent type is not known
200
200
  """
201
+ from pathlib import Path
202
+
201
203
  # Check built-in agents first
202
204
  if subagent_type in SUBAGENT_PROMPTS:
203
205
  return SUBAGENT_PROMPTS[subagent_type]
@@ -206,10 +208,80 @@ def get_subagent_prompt(subagent_type: str, repo_root=None) -> str:
206
208
  from ..toolkits import get_custom_agent
207
209
  custom_agent = get_custom_agent(subagent_type, repo_root)
208
210
  if custom_agent:
209
- return custom_agent.system_prompt
211
+ prompt_parts = [custom_agent.system_prompt]
212
+
213
+ # Inject rules if specified
214
+ if custom_agent.rules:
215
+ rules_content = _load_rules_by_names(custom_agent.rules, repo_root)
216
+ if rules_content:
217
+ prompt_parts.append(f"\n\n## Rules\n\n{rules_content}")
218
+
219
+ # Inject skills if specified
220
+ if custom_agent.skills:
221
+ skills_content = _load_skills_by_names(custom_agent.skills, repo_root)
222
+ if skills_content:
223
+ prompt_parts.append(f"\n\n## Skills\n\n{skills_content}")
224
+
225
+ return "".join(prompt_parts)
210
226
 
211
227
  # Not found
212
228
  available = list(SUBAGENT_PROMPTS.keys())
213
229
  raise ValueError(
214
230
  f"Unknown agent type: {subagent_type}. Available: {available}"
215
231
  )
232
+
233
+
234
+ def _load_rules_by_names(rule_names: list[str], repo_root=None) -> str:
235
+ """Load specific rules by name.
236
+
237
+ Args:
238
+ rule_names: List of rule names to load
239
+ repo_root: Repository root
240
+
241
+ Returns:
242
+ Combined rules content
243
+ """
244
+ from pathlib import Path
245
+
246
+ rules_dir = (repo_root or Path.cwd()) / ".emdash" / "rules"
247
+ if not rules_dir.exists():
248
+ return ""
249
+
250
+ parts = []
251
+ for name in rule_names:
252
+ rule_file = rules_dir / f"{name}.md"
253
+ if rule_file.exists():
254
+ try:
255
+ content = rule_file.read_text().strip()
256
+ if content:
257
+ parts.append(content)
258
+ except Exception:
259
+ pass
260
+
261
+ return "\n\n---\n\n".join(parts)
262
+
263
+
264
+ def _load_skills_by_names(skill_names: list[str], repo_root=None) -> str:
265
+ """Load specific skills by name and return their instructions.
266
+
267
+ Args:
268
+ skill_names: List of skill names to load
269
+ repo_root: Repository root
270
+
271
+ Returns:
272
+ Combined skills instructions
273
+ """
274
+ from pathlib import Path
275
+ from ..skills import SkillRegistry
276
+
277
+ skills_dir = (repo_root or Path.cwd()) / ".emdash" / "skills"
278
+ registry = SkillRegistry.get_instance()
279
+ registry.load_skills(skills_dir)
280
+
281
+ parts = []
282
+ for name in skill_names:
283
+ skill = registry.get_skill(name)
284
+ if skill and skill.instructions:
285
+ parts.append(f"### {skill.name}\n\n{skill.instructions}")
286
+
287
+ return "\n\n".join(parts)