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,449 @@
1
+ """Planner agent for creating research plans.
2
+
3
+ The Planner decomposes research goals into questions that map to
4
+ how the team works. It aligns with team values by:
5
+ - V3: Creating reviewer-first output sections
6
+ - V4: Setting budgets for cost awareness
7
+ - V5: Ensuring actionable outcomes
8
+ - V6: Using team vocabulary
9
+ """
10
+
11
+ import json
12
+ from typing import Optional
13
+
14
+ from rich.console import Console
15
+
16
+ from ..providers import get_provider
17
+ from ..providers.factory import DEFAULT_MODEL
18
+ from .state import (
19
+ ResearchPlan,
20
+ ResearchQuestion,
21
+ )
22
+ from .macros import suggest_macros, TOOL_MACROS
23
+
24
+
25
+ # Valid tool names that can be suggested
26
+ VALID_TOOLS = set(TOOL_MACROS.keys())
27
+
28
+
29
+ # Standard questions aligned with team workflows
30
+ STANDARD_QUESTIONS = [
31
+ {
32
+ "category": "feature",
33
+ "template": "What is the feature/behavior of {topic}?",
34
+ "priority": "P0",
35
+ "deliverable": "Design",
36
+ "criteria": ["Entry points identified", "Main functionality described"],
37
+ },
38
+ {
39
+ "category": "implementation",
40
+ "template": "Where is {topic} implemented?",
41
+ "priority": "P0",
42
+ "deliverable": "Implementation",
43
+ "criteria": ["File paths found", "Key functions/classes identified"],
44
+ },
45
+ {
46
+ "category": "dependencies",
47
+ "template": "What depends on {topic}?",
48
+ "priority": "P1",
49
+ "deliverable": "Implementation",
50
+ "criteria": ["Callers identified", "Dependency graph understood"],
51
+ },
52
+ {
53
+ "category": "ownership",
54
+ "template": "Who owns or touches {topic}?",
55
+ "priority": "P1",
56
+ "deliverable": "Review",
57
+ "criteria": ["Authors identified", "Expertise areas mapped"],
58
+ },
59
+ {
60
+ "category": "risk",
61
+ "template": "What's risky about {topic}?",
62
+ "priority": "P1",
63
+ "deliverable": "Review",
64
+ "criteria": ["Risk factors identified", "Impact scope assessed"],
65
+ },
66
+ {
67
+ "category": "testing",
68
+ "template": "What tests/CI validate {topic}?",
69
+ "priority": "P2",
70
+ "deliverable": "Testing",
71
+ "criteria": ["Test files identified", "Coverage understood"],
72
+ },
73
+ {
74
+ "category": "review",
75
+ "template": "What should a reviewer check for {topic}?",
76
+ "priority": "P1",
77
+ "deliverable": "Review",
78
+ "criteria": ["Review checklist created", "Critical paths identified"],
79
+ },
80
+ ]
81
+
82
+ # Required sections in final report (V3: Reviewer-first)
83
+ REQUIRED_SECTIONS = [
84
+ "Findings (fact-grounded)",
85
+ "Evidence Coverage Matrix",
86
+ "Design/Spec Implications",
87
+ "Risks & Unknowns",
88
+ "Recommended Tasks",
89
+ "Reviewer Checklist",
90
+ "Tooling Summary",
91
+ ]
92
+
93
+ # Team values checklist for Critic
94
+ TEAM_VALUES_CHECKLIST = [
95
+ "V1: All claims have evidence IDs (no ungrounded statements)",
96
+ "V2: Evidence is reproducible (tool calls documented)",
97
+ "V3: Output includes reviewer checklist and acceptance criteria",
98
+ "V4: Budget was respected (no wasteful tool calls)",
99
+ "V5: Report ends with actionable tasks",
100
+ "V6: Uses team vocabulary (tasks, PRs, reviewers)",
101
+ ]
102
+
103
+ # Default budgets (V4: Cost awareness)
104
+ DEFAULT_BUDGETS = {
105
+ "tool_calls": 100,
106
+ "tokens": 150000,
107
+ "time_s": 600, # 10 minutes
108
+ }
109
+
110
+
111
+ PLANNER_SYSTEM_PROMPT = """You are a research planner that creates structured research plans.
112
+
113
+ Your job is to decompose a research goal into questions that align with team workflows.
114
+
115
+ TEAM VALUES YOU MUST RESPECT:
116
+ - V1: Truth over fluency - prefer "unknown" over guesses
117
+ - V2: Evidence-first - all claims must be backed by tool outputs
118
+ - V3: Reviewer-first - output must include review checklists
119
+ - V4: Cost awareness - minimize tool calls, start with cheap models
120
+ - V5: Actionable outcomes - end with concrete tasks
121
+ - V6: Team alignment - use team vocabulary
122
+
123
+ QUESTION CATEGORIES:
124
+ 1. Feature/Behavior - What is it?
125
+ 2. Implementation - Where is the code?
126
+ 3. Dependencies - What depends on it?
127
+ 4. Ownership - Who owns/touches it?
128
+ 5. Risk - What could go wrong?
129
+ 6. Testing - How is it tested?
130
+ 7. Review - What should reviewers check?
131
+
132
+ AVAILABLE TOOL MACROS (only use these names):
133
+ - deep_feature_analysis: Understand feature behavior and impact
134
+ - team_activity_analysis: Find owners, expertise, velocity risks
135
+ - architectural_deep_dive: Map architecture and key modules
136
+ - implementation_trace: Trace specific implementation paths
137
+ - risk_assessment: Assess risks of modifications
138
+ - pr_context: Understand PR and change context
139
+
140
+ OUTPUT FORMAT:
141
+ Return a JSON object with:
142
+ {
143
+ "topic": "extracted topic from goal",
144
+ "questions": [
145
+ {
146
+ "qid": "Q1",
147
+ "question": "specific question",
148
+ "priority": "P0|P1|P2",
149
+ "success_criteria": ["criterion 1", "criterion 2"],
150
+ "suggested_tools": ["deep_feature_analysis"],
151
+ "deliverable": "Design|Implementation|Testing|Review|Ops"
152
+ }
153
+ ],
154
+ "max_iterations": 5,
155
+ "budgets": {
156
+ "tool_calls": 100,
157
+ "tokens": 150000,
158
+ "time_s": 600
159
+ }
160
+ }
161
+
162
+ PRIORITIES:
163
+ - P0: Must answer (blocking)
164
+ - P1: Should answer (important)
165
+ - P2: Nice to have (optional)
166
+
167
+ Be specific in questions. Avoid vague language. Every question should be answerable with tools.
168
+ IMPORTANT: Only suggest tools from the AVAILABLE TOOL MACROS list above."""
169
+
170
+
171
+ class PlannerAgent:
172
+ """Creates research plans with team-aligned questions.
173
+
174
+ The Planner analyzes the research goal and creates a structured
175
+ plan with prioritized questions, budgets, and success criteria.
176
+ """
177
+
178
+ def __init__(
179
+ self,
180
+ model: str = DEFAULT_MODEL,
181
+ verbose: bool = True,
182
+ ):
183
+ """Initialize the planner agent.
184
+
185
+ Args:
186
+ model: LLM model to use
187
+ verbose: Whether to print progress
188
+ """
189
+ self.provider = get_provider(model)
190
+ self.model = model
191
+ self.verbose = verbose
192
+ self.console = Console()
193
+
194
+ def create_plan(
195
+ self,
196
+ goal: str,
197
+ context: str = "",
198
+ max_iterations: int = 3,
199
+ budgets: Optional[dict] = None,
200
+ ) -> ResearchPlan:
201
+ """Create a research plan for a goal.
202
+
203
+ Args:
204
+ goal: The research goal
205
+ context: Additional context
206
+ max_iterations: Maximum research iterations
207
+ budgets: Resource budgets (tool_calls, tokens, time_s)
208
+
209
+ Returns:
210
+ ResearchPlan with questions and budgets
211
+ """
212
+ if self.verbose:
213
+ self.console.print(f"[cyan]Planning research for:[/cyan] {goal}")
214
+
215
+ # Try LLM-based planning first
216
+ try:
217
+ plan = self._llm_plan(goal, context, max_iterations, budgets)
218
+ if plan and plan.questions:
219
+ if self.verbose:
220
+ self.console.print(f"[green]Created plan with {len(plan.questions)} questions[/green]")
221
+ return plan
222
+ except Exception as e:
223
+ if self.verbose:
224
+ self.console.print(f"[yellow]LLM planning failed: {e}. Using template.[/yellow]")
225
+
226
+ # Fallback to template-based planning
227
+ return self._template_plan(goal, context, max_iterations, budgets)
228
+
229
+ def _llm_plan(
230
+ self,
231
+ goal: str,
232
+ context: str,
233
+ max_iterations: int,
234
+ budgets: Optional[dict],
235
+ ) -> Optional[ResearchPlan]:
236
+ """Create plan using LLM.
237
+
238
+ Args:
239
+ goal: Research goal
240
+ context: Additional context
241
+ max_iterations: Max iterations
242
+ budgets: Resource budgets
243
+
244
+ Returns:
245
+ ResearchPlan or None on failure
246
+ """
247
+ user_message = f"""Create a research plan for this goal:
248
+
249
+ GOAL: {goal}
250
+
251
+ {f'CONTEXT: {context}' if context else ''}
252
+
253
+ Create questions that will help understand this topic thoroughly.
254
+ Prioritize P0 questions that are essential to answer.
255
+ Suggest appropriate tool macros for each question.
256
+
257
+ Return JSON only, no markdown code blocks."""
258
+
259
+ messages = [
260
+ {"role": "user", "content": user_message},
261
+ ]
262
+
263
+ response = self.provider.chat(messages, system=PLANNER_SYSTEM_PROMPT)
264
+ content = response.content or ""
265
+
266
+ # Parse JSON from response
267
+ try:
268
+ # Try to extract JSON from response
269
+ json_str = content
270
+ if "```" in content:
271
+ # Extract from code block
272
+ start = content.find("```")
273
+ end = content.find("```", start + 3)
274
+ if end > start:
275
+ json_str = content[start + 3:end]
276
+ if json_str.startswith("json"):
277
+ json_str = json_str[4:]
278
+
279
+ data = json.loads(json_str.strip())
280
+
281
+ # Build questions
282
+ questions = []
283
+ for i, q in enumerate(data.get("questions", [])):
284
+ # Filter suggested tools to only include valid ones
285
+ raw_tools = q.get("suggested_tools", [])
286
+ valid_suggested = [t for t in raw_tools if t in VALID_TOOLS]
287
+ # If no valid tools, use suggest_macros to pick appropriate ones
288
+ if not valid_suggested:
289
+ valid_suggested = suggest_macros(q.get("question", ""))
290
+
291
+ questions.append(ResearchQuestion(
292
+ qid=q.get("qid", f"Q{i+1}"),
293
+ question=q["question"],
294
+ priority=q.get("priority", "P1"),
295
+ success_criteria=q.get("success_criteria", []),
296
+ suggested_tools=valid_suggested,
297
+ deliverable=q.get("deliverable", "Implementation"),
298
+ ))
299
+
300
+ if not questions:
301
+ return None
302
+
303
+ # Use provided budgets or defaults
304
+ final_budgets = budgets or data.get("budgets", DEFAULT_BUDGETS)
305
+
306
+ return ResearchPlan(
307
+ goal=goal,
308
+ questions=questions,
309
+ max_iterations=data.get("max_iterations", max_iterations),
310
+ budgets=final_budgets,
311
+ required_sections=REQUIRED_SECTIONS,
312
+ team_values_checklist=TEAM_VALUES_CHECKLIST,
313
+ )
314
+
315
+ except (json.JSONDecodeError, KeyError, TypeError):
316
+ return None
317
+
318
+ def _template_plan(
319
+ self,
320
+ goal: str,
321
+ context: str,
322
+ max_iterations: int,
323
+ budgets: Optional[dict],
324
+ ) -> ResearchPlan:
325
+ """Create plan using templates.
326
+
327
+ Fallback method that generates questions from standard templates.
328
+
329
+ Args:
330
+ goal: Research goal
331
+ context: Additional context
332
+ max_iterations: Max iterations
333
+ budgets: Resource budgets
334
+
335
+ Returns:
336
+ ResearchPlan
337
+ """
338
+ # Extract topic from goal
339
+ topic = self._extract_topic(goal)
340
+
341
+ # Generate questions from templates
342
+ questions = []
343
+ suggested_macros = suggest_macros(goal)
344
+
345
+ for i, template in enumerate(STANDARD_QUESTIONS):
346
+ question_text = template["template"].format(topic=topic)
347
+
348
+ # Suggest macros based on category
349
+ tools = []
350
+ if template["category"] == "feature":
351
+ tools = ["deep_feature_analysis"]
352
+ elif template["category"] == "implementation":
353
+ tools = ["implementation_trace"]
354
+ elif template["category"] == "dependencies":
355
+ tools = ["deep_feature_analysis"]
356
+ elif template["category"] == "ownership":
357
+ tools = ["team_activity_analysis"]
358
+ elif template["category"] == "risk":
359
+ tools = ["risk_assessment"]
360
+ elif template["category"] == "testing":
361
+ tools = ["implementation_trace"]
362
+ elif template["category"] == "review":
363
+ tools = ["risk_assessment", "team_activity_analysis"]
364
+
365
+ questions.append(ResearchQuestion(
366
+ qid=f"Q{i+1}",
367
+ question=question_text,
368
+ priority=template["priority"],
369
+ success_criteria=template["criteria"],
370
+ suggested_tools=tools,
371
+ deliverable=template["deliverable"],
372
+ ))
373
+
374
+ return ResearchPlan(
375
+ goal=goal,
376
+ questions=questions,
377
+ max_iterations=max_iterations,
378
+ budgets=budgets or DEFAULT_BUDGETS,
379
+ required_sections=REQUIRED_SECTIONS,
380
+ team_values_checklist=TEAM_VALUES_CHECKLIST,
381
+ )
382
+
383
+ def _extract_topic(self, goal: str) -> str:
384
+ """Extract the main topic from a goal.
385
+
386
+ Simple heuristic extraction. Could be improved with NLP.
387
+
388
+ Args:
389
+ goal: The research goal
390
+
391
+ Returns:
392
+ Extracted topic
393
+ """
394
+ # Remove common prefixes
395
+ topic = goal
396
+ prefixes = [
397
+ "how does", "what is", "where is", "who owns",
398
+ "explain", "understand", "investigate", "research",
399
+ "analyze", "find", "look into",
400
+ ]
401
+
402
+ topic_lower = topic.lower()
403
+ for prefix in prefixes:
404
+ if topic_lower.startswith(prefix):
405
+ topic = topic[len(prefix):].strip()
406
+ break
407
+
408
+ # Remove trailing punctuation
409
+ topic = topic.rstrip("?!.")
410
+
411
+ # Clean up "the" at start
412
+ if topic.lower().startswith("the "):
413
+ topic = topic[4:]
414
+
415
+ return topic.strip() or goal
416
+
417
+ def adjust_plan(
418
+ self,
419
+ plan: ResearchPlan,
420
+ critique_feedback: list[str],
421
+ drop_p2: bool = False,
422
+ ) -> ResearchPlan:
423
+ """Adjust plan based on critique feedback.
424
+
425
+ Called when budget is running low or Critic requests changes.
426
+
427
+ Args:
428
+ plan: Current plan
429
+ critique_feedback: Feedback from Critic
430
+ drop_p2: Whether to drop P2 questions
431
+
432
+ Returns:
433
+ Adjusted ResearchPlan
434
+ """
435
+ questions = list(plan.questions)
436
+
437
+ if drop_p2:
438
+ questions = [q for q in questions if q.priority != "P2"]
439
+
440
+ # Could add more sophisticated adjustment based on feedback
441
+
442
+ return ResearchPlan(
443
+ goal=plan.goal,
444
+ questions=questions,
445
+ max_iterations=plan.max_iterations,
446
+ budgets=plan.budgets,
447
+ required_sections=plan.required_sections,
448
+ team_values_checklist=plan.team_values_checklist,
449
+ )