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,469 @@
1
+ """Tool macros for reproducible research workflows.
2
+
3
+ This module provides predefined tool sequences (macros) that can be
4
+ executed to gather specific types of evidence.
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+ from typing import Any, Optional
9
+
10
+ from .state import EvidenceItem
11
+ from ...utils.logger import log
12
+
13
+
14
+ @dataclass
15
+ class MacroStep:
16
+ """A single step in a tool macro.
17
+
18
+ Attributes:
19
+ tool: Tool name to execute
20
+ params: Parameters for the tool
21
+ depends_on: Previous step whose output is needed
22
+ param_mappings: Map output fields to this step's params
23
+ """
24
+ tool: str
25
+ params: dict = field(default_factory=dict)
26
+ depends_on: Optional[str] = None
27
+ param_mappings: dict = field(default_factory=dict)
28
+
29
+
30
+ @dataclass
31
+ class ToolMacro:
32
+ """A sequence of tool calls for gathering evidence.
33
+
34
+ Attributes:
35
+ name: Macro identifier
36
+ description: What this macro investigates
37
+ steps: Ordered list of steps
38
+ output_schema: Expected output fields
39
+ """
40
+ name: str
41
+ description: str
42
+ steps: list[MacroStep]
43
+ output_schema: dict = field(default_factory=dict)
44
+
45
+
46
+ # Predefined macros for common research patterns
47
+ TOOL_MACROS = {
48
+ "deep_feature_analysis": ToolMacro(
49
+ name="deep_feature_analysis",
50
+ description="Understand feature behavior and impact",
51
+ steps=[
52
+ MacroStep(
53
+ tool="semantic_search",
54
+ params={"query": "{topic}", "limit": 10},
55
+ ),
56
+ MacroStep(
57
+ tool="expand_node",
58
+ params={"node_type": "Function", "identifier": "{identifier}"},
59
+ depends_on="semantic_search",
60
+ param_mappings={"identifier": "results[0].qualified_name"},
61
+ ),
62
+ MacroStep(
63
+ tool="get_callers",
64
+ params={"qualified_name": "{identifier}"},
65
+ depends_on="expand_node",
66
+ ),
67
+ MacroStep(
68
+ tool="get_callees",
69
+ params={"qualified_name": "{identifier}"},
70
+ depends_on="expand_node",
71
+ ),
72
+ ],
73
+ ),
74
+
75
+ "team_activity_analysis": ToolMacro(
76
+ name="team_activity_analysis",
77
+ description="Find owners, expertise, and velocity risks",
78
+ steps=[
79
+ MacroStep(
80
+ tool="semantic_search",
81
+ params={"query": "{topic}", "limit": 5},
82
+ ),
83
+ MacroStep(
84
+ tool="get_file_dependencies",
85
+ params={"file_path": "{file_path}"},
86
+ depends_on="semantic_search",
87
+ param_mappings={"file_path": "results[0].file_path"},
88
+ ),
89
+ ],
90
+ ),
91
+
92
+ "architectural_deep_dive": ToolMacro(
93
+ name="architectural_deep_dive",
94
+ description="Map architecture and key modules",
95
+ steps=[
96
+ MacroStep(
97
+ tool="get_communities",
98
+ params={"limit": 10, "include_members": True},
99
+ ),
100
+ MacroStep(
101
+ tool="get_top_pagerank",
102
+ params={"limit": 15},
103
+ ),
104
+ MacroStep(
105
+ tool="get_area_importance",
106
+ params={"area_type": "directory", "limit": 10},
107
+ ),
108
+ ],
109
+ ),
110
+
111
+ "implementation_trace": ToolMacro(
112
+ name="implementation_trace",
113
+ description="Trace specific implementation paths",
114
+ steps=[
115
+ MacroStep(
116
+ tool="semantic_search",
117
+ params={"query": "{topic}", "limit": 10},
118
+ ),
119
+ MacroStep(
120
+ tool="expand_node",
121
+ params={"node_type": "Function", "identifier": "{identifier}"},
122
+ depends_on="semantic_search",
123
+ param_mappings={"identifier": "results[0].qualified_name"},
124
+ ),
125
+ MacroStep(
126
+ tool="get_callees",
127
+ params={"qualified_name": "{identifier}", "limit": 20},
128
+ depends_on="expand_node",
129
+ ),
130
+ ],
131
+ ),
132
+
133
+ "risk_assessment": ToolMacro(
134
+ name="risk_assessment",
135
+ description="Assess risks of modifications",
136
+ steps=[
137
+ MacroStep(
138
+ tool="semantic_search",
139
+ params={"query": "{topic}", "limit": 5},
140
+ ),
141
+ MacroStep(
142
+ tool="get_impact_analysis",
143
+ params={
144
+ "entity_type": "Function",
145
+ "identifier": "{identifier}",
146
+ "depth": 2,
147
+ },
148
+ depends_on="semantic_search",
149
+ param_mappings={"identifier": "results[0].qualified_name"},
150
+ ),
151
+ MacroStep(
152
+ tool="get_callers",
153
+ params={"qualified_name": "{identifier}", "limit": 30},
154
+ depends_on="semantic_search",
155
+ param_mappings={"qualified_name": "results[0].qualified_name"},
156
+ ),
157
+ ],
158
+ ),
159
+
160
+ "pr_context": ToolMacro(
161
+ name="pr_context",
162
+ description="Understand PR and change context",
163
+ steps=[
164
+ MacroStep(
165
+ tool="github_list_prs",
166
+ params={"owner": "{owner}", "repo": "{repo}", "state": "all", "per_page": 10},
167
+ ),
168
+ ],
169
+ ),
170
+ }
171
+
172
+
173
+ class MacroExecutor:
174
+ """Executes tool macros and collects evidence.
175
+
176
+ The executor handles:
177
+ - Step sequencing and dependencies
178
+ - Parameter resolution from prior outputs
179
+ - Evidence collection with unique IDs
180
+ - Budget tracking
181
+ """
182
+
183
+ def __init__(self, toolkit: Any):
184
+ """Initialize the executor.
185
+
186
+ Args:
187
+ toolkit: AgentToolkit instance for executing tools
188
+ """
189
+ self.toolkit = toolkit
190
+ self.evidence_counter = 0
191
+
192
+ def execute_macro(
193
+ self,
194
+ macro_name: str,
195
+ params: dict,
196
+ budget_remaining: int = 50,
197
+ prior_context: Optional[dict] = None,
198
+ ) -> tuple[list[EvidenceItem], dict]:
199
+ """Execute a macro and return evidence.
200
+
201
+ Args:
202
+ macro_name: Name of the macro to execute
203
+ params: Initial parameters
204
+ budget_remaining: Maximum tool calls allowed
205
+ prior_context: Context from previous iterations
206
+
207
+ Returns:
208
+ Tuple of (evidence_items, updated_context)
209
+ """
210
+ if macro_name not in TOOL_MACROS:
211
+ log.warning(f"Unknown macro: {macro_name}")
212
+ return [], {}
213
+
214
+ macro = TOOL_MACROS[macro_name]
215
+ evidence: list[EvidenceItem] = []
216
+ context = dict(prior_context or {})
217
+ step_outputs: dict[str, Any] = {}
218
+
219
+ for step in macro.steps:
220
+ if len(evidence) >= budget_remaining:
221
+ break
222
+
223
+ # Resolve parameters
224
+ resolved_params = self._resolve_params(
225
+ step.params,
226
+ params,
227
+ step_outputs,
228
+ step.param_mappings,
229
+ context,
230
+ )
231
+
232
+ # Skip if required params are missing
233
+ if None in resolved_params.values():
234
+ log.debug(f"Skipping step {step.tool}: missing params")
235
+ continue
236
+
237
+ try:
238
+ result = self.toolkit.execute(step.tool, **resolved_params)
239
+
240
+ if result.success:
241
+ # Store output for dependent steps
242
+ step_outputs[step.tool] = result.data
243
+
244
+ # Create evidence
245
+ self.evidence_counter += 1
246
+ ev = EvidenceItem(
247
+ id=f"E{self.evidence_counter}",
248
+ tool=step.tool,
249
+ input=resolved_params,
250
+ output_ref=f"result_{self.evidence_counter}",
251
+ summary=self._summarize_result(step.tool, result.data),
252
+ entities=self._extract_entities(result.data),
253
+ )
254
+ evidence.append(ev)
255
+
256
+ # Update context
257
+ if "results" in result.data and result.data["results"]:
258
+ context["last_search_results"] = result.data["results"]
259
+ context["last_search_top"] = result.data["results"][0]
260
+
261
+ except Exception as e:
262
+ log.warning(f"Macro step {step.tool} failed: {e}")
263
+
264
+ return evidence, context
265
+
266
+ def _resolve_params(
267
+ self,
268
+ step_params: dict,
269
+ initial_params: dict,
270
+ step_outputs: dict,
271
+ mappings: dict,
272
+ context: dict,
273
+ ) -> dict:
274
+ """Resolve parameter placeholders.
275
+
276
+ Args:
277
+ step_params: Parameters with {placeholders}
278
+ initial_params: Initial params from caller
279
+ step_outputs: Outputs from previous steps
280
+ mappings: Output field to param mappings
281
+ context: Prior context
282
+
283
+ Returns:
284
+ Resolved parameters
285
+ """
286
+ resolved = {}
287
+
288
+ for key, value in step_params.items():
289
+ if isinstance(value, str) and value.startswith("{") and value.endswith("}"):
290
+ placeholder = value[1:-1]
291
+
292
+ # Check mappings first
293
+ if key in mappings:
294
+ resolved[key] = self._resolve_mapping(mappings[key], step_outputs, context)
295
+ # Then initial params
296
+ elif placeholder in initial_params:
297
+ resolved[key] = initial_params[placeholder]
298
+ # Then context
299
+ elif placeholder in context:
300
+ resolved[key] = context[placeholder]
301
+ # Try to get from last search results
302
+ elif placeholder == "identifier" and "last_search_top" in context:
303
+ top = context["last_search_top"]
304
+ resolved[key] = top.get("qualified_name") or top.get("file_path")
305
+ else:
306
+ resolved[key] = None
307
+ else:
308
+ resolved[key] = value
309
+
310
+ return resolved
311
+
312
+ def _resolve_mapping(
313
+ self,
314
+ mapping: str,
315
+ step_outputs: dict,
316
+ context: dict,
317
+ ) -> Optional[str]:
318
+ """Resolve an output mapping like 'results[0].qualified_name'.
319
+
320
+ Args:
321
+ mapping: Mapping string
322
+ step_outputs: Previous step outputs
323
+ context: Prior context
324
+
325
+ Returns:
326
+ Resolved value or None
327
+ """
328
+ try:
329
+ parts = mapping.split(".")
330
+
331
+ # Get base value
332
+ base = parts[0]
333
+ if "[" in base:
334
+ # Array access: results[0]
335
+ array_name = base[:base.index("[")]
336
+ index = int(base[base.index("[") + 1:base.index("]")])
337
+
338
+ # Look in step outputs first, then context
339
+ if array_name in step_outputs:
340
+ value = step_outputs[array_name][index]
341
+ elif array_name in context:
342
+ value = context[array_name][index]
343
+ else:
344
+ return None
345
+ else:
346
+ value = step_outputs.get(base) or context.get(base)
347
+
348
+ # Navigate to nested value
349
+ for part in parts[1:]:
350
+ if isinstance(value, dict):
351
+ value = value.get(part)
352
+ else:
353
+ return None
354
+
355
+ return value
356
+
357
+ except (IndexError, KeyError, TypeError):
358
+ return None
359
+
360
+ def _summarize_result(self, tool: str, data: dict) -> str:
361
+ """Create a brief summary of a tool result."""
362
+ if "results" in data:
363
+ return f"Found {len(data['results'])} results"
364
+ elif "root_node" in data:
365
+ node = data["root_node"]
366
+ return f"Expanded {node.get('qualified_name', 'node')}"
367
+ elif "callers" in data:
368
+ return f"Found {len(data['callers'])} callers"
369
+ elif "callees" in data:
370
+ return f"Found {len(data['callees'])} callees"
371
+ elif "communities" in data:
372
+ return f"Found {len(data['communities'])} communities"
373
+ else:
374
+ return f"{tool} completed"
375
+
376
+ def _extract_entities(self, data: dict) -> list[str]:
377
+ """Extract entity identifiers from result data."""
378
+ entities = []
379
+
380
+ if "results" in data:
381
+ for item in data["results"][:10]:
382
+ if isinstance(item, dict):
383
+ for key in ["qualified_name", "file_path", "identifier"]:
384
+ if key in item and item[key]:
385
+ entities.append(str(item[key]))
386
+ break
387
+
388
+ if "root_node" in data:
389
+ root = data["root_node"]
390
+ for key in ["qualified_name", "file_path"]:
391
+ if key in root and root[key]:
392
+ entities.append(str(root[key]))
393
+
394
+ if "callers" in data:
395
+ for caller in data["callers"][:5]:
396
+ if isinstance(caller, dict) and "qualified_name" in caller:
397
+ entities.append(caller["qualified_name"])
398
+
399
+ if "callees" in data:
400
+ for callee in data["callees"][:5]:
401
+ if isinstance(callee, dict) and "qualified_name" in callee:
402
+ entities.append(callee["qualified_name"])
403
+
404
+ return list(set(entities))[:20]
405
+
406
+
407
+ def get_macro(name: str) -> Optional[ToolMacro]:
408
+ """Get a macro by name.
409
+
410
+ Args:
411
+ name: Macro name
412
+
413
+ Returns:
414
+ ToolMacro or None
415
+ """
416
+ return TOOL_MACROS.get(name)
417
+
418
+
419
+ def list_macros() -> list[str]:
420
+ """List all available macro names.
421
+
422
+ Returns:
423
+ List of macro names
424
+ """
425
+ return list(TOOL_MACROS.keys())
426
+
427
+
428
+ def suggest_macros(
429
+ goal: str,
430
+ include_github: bool = False,
431
+ ) -> list[str]:
432
+ """Suggest macros based on a goal.
433
+
434
+ Args:
435
+ goal: Research goal
436
+ include_github: Whether to include GitHub macros
437
+
438
+ Returns:
439
+ List of suggested macro names
440
+ """
441
+ goal_lower = goal.lower()
442
+ suggestions = []
443
+
444
+ # Feature/implementation questions
445
+ if any(word in goal_lower for word in ["how", "what", "feature", "work", "implement"]):
446
+ suggestions.append("deep_feature_analysis")
447
+ suggestions.append("implementation_trace")
448
+
449
+ # Architecture questions
450
+ if any(word in goal_lower for word in ["architecture", "structure", "module", "component"]):
451
+ suggestions.append("architectural_deep_dive")
452
+
453
+ # Risk/impact questions
454
+ if any(word in goal_lower for word in ["risk", "impact", "change", "modify", "refactor"]):
455
+ suggestions.append("risk_assessment")
456
+
457
+ # Team/ownership questions
458
+ if any(word in goal_lower for word in ["who", "owner", "team", "expertise"]):
459
+ suggestions.append("team_activity_analysis")
460
+
461
+ # PR/history questions
462
+ if include_github and any(word in goal_lower for word in ["pr", "pull", "change", "history"]):
463
+ suggestions.append("pr_context")
464
+
465
+ # Default if nothing matched
466
+ if not suggestions:
467
+ suggestions = ["deep_feature_analysis", "architectural_deep_dive"]
468
+
469
+ return suggestions[:3] # Return top 3