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.
- emdash_core/__init__.py +3 -0
- emdash_core/agent/__init__.py +37 -0
- emdash_core/agent/agents.py +225 -0
- emdash_core/agent/code_reviewer.py +476 -0
- emdash_core/agent/compaction.py +143 -0
- emdash_core/agent/context_manager.py +140 -0
- emdash_core/agent/events.py +338 -0
- emdash_core/agent/handlers.py +224 -0
- emdash_core/agent/inprocess_subagent.py +377 -0
- emdash_core/agent/mcp/__init__.py +50 -0
- emdash_core/agent/mcp/client.py +346 -0
- emdash_core/agent/mcp/config.py +302 -0
- emdash_core/agent/mcp/manager.py +496 -0
- emdash_core/agent/mcp/tool_factory.py +213 -0
- emdash_core/agent/prompts/__init__.py +38 -0
- emdash_core/agent/prompts/main_agent.py +104 -0
- emdash_core/agent/prompts/subagents.py +131 -0
- emdash_core/agent/prompts/workflow.py +136 -0
- emdash_core/agent/providers/__init__.py +34 -0
- emdash_core/agent/providers/base.py +143 -0
- emdash_core/agent/providers/factory.py +80 -0
- emdash_core/agent/providers/models.py +220 -0
- emdash_core/agent/providers/openai_provider.py +463 -0
- emdash_core/agent/providers/transformers_provider.py +217 -0
- emdash_core/agent/research/__init__.py +81 -0
- emdash_core/agent/research/agent.py +143 -0
- emdash_core/agent/research/controller.py +254 -0
- emdash_core/agent/research/critic.py +428 -0
- emdash_core/agent/research/macros.py +469 -0
- emdash_core/agent/research/planner.py +449 -0
- emdash_core/agent/research/researcher.py +436 -0
- emdash_core/agent/research/state.py +523 -0
- emdash_core/agent/research/synthesizer.py +594 -0
- emdash_core/agent/reviewer_profile.py +475 -0
- emdash_core/agent/rules.py +123 -0
- emdash_core/agent/runner.py +601 -0
- emdash_core/agent/session.py +262 -0
- emdash_core/agent/spec_schema.py +66 -0
- emdash_core/agent/specification.py +479 -0
- emdash_core/agent/subagent.py +397 -0
- emdash_core/agent/subagent_prompts.py +13 -0
- emdash_core/agent/toolkit.py +482 -0
- emdash_core/agent/toolkits/__init__.py +64 -0
- emdash_core/agent/toolkits/base.py +96 -0
- emdash_core/agent/toolkits/explore.py +47 -0
- emdash_core/agent/toolkits/plan.py +55 -0
- emdash_core/agent/tools/__init__.py +141 -0
- emdash_core/agent/tools/analytics.py +436 -0
- emdash_core/agent/tools/base.py +131 -0
- emdash_core/agent/tools/coding.py +484 -0
- emdash_core/agent/tools/github_mcp.py +592 -0
- emdash_core/agent/tools/history.py +13 -0
- emdash_core/agent/tools/modes.py +153 -0
- emdash_core/agent/tools/plan.py +206 -0
- emdash_core/agent/tools/plan_write.py +135 -0
- emdash_core/agent/tools/search.py +412 -0
- emdash_core/agent/tools/spec.py +341 -0
- emdash_core/agent/tools/task.py +262 -0
- emdash_core/agent/tools/task_output.py +204 -0
- emdash_core/agent/tools/tasks.py +454 -0
- emdash_core/agent/tools/traversal.py +588 -0
- emdash_core/agent/tools/web.py +179 -0
- emdash_core/analytics/__init__.py +5 -0
- emdash_core/analytics/engine.py +1286 -0
- emdash_core/api/__init__.py +5 -0
- emdash_core/api/agent.py +308 -0
- emdash_core/api/agents.py +154 -0
- emdash_core/api/analyze.py +264 -0
- emdash_core/api/auth.py +173 -0
- emdash_core/api/context.py +77 -0
- emdash_core/api/db.py +121 -0
- emdash_core/api/embed.py +131 -0
- emdash_core/api/feature.py +143 -0
- emdash_core/api/health.py +93 -0
- emdash_core/api/index.py +162 -0
- emdash_core/api/plan.py +110 -0
- emdash_core/api/projectmd.py +210 -0
- emdash_core/api/query.py +320 -0
- emdash_core/api/research.py +122 -0
- emdash_core/api/review.py +161 -0
- emdash_core/api/router.py +76 -0
- emdash_core/api/rules.py +116 -0
- emdash_core/api/search.py +119 -0
- emdash_core/api/spec.py +99 -0
- emdash_core/api/swarm.py +223 -0
- emdash_core/api/tasks.py +109 -0
- emdash_core/api/team.py +120 -0
- emdash_core/auth/__init__.py +17 -0
- emdash_core/auth/github.py +389 -0
- emdash_core/config.py +74 -0
- emdash_core/context/__init__.py +52 -0
- emdash_core/context/models.py +50 -0
- emdash_core/context/providers/__init__.py +11 -0
- emdash_core/context/providers/base.py +74 -0
- emdash_core/context/providers/explored_areas.py +183 -0
- emdash_core/context/providers/touched_areas.py +360 -0
- emdash_core/context/registry.py +73 -0
- emdash_core/context/reranker.py +199 -0
- emdash_core/context/service.py +260 -0
- emdash_core/context/session.py +352 -0
- emdash_core/core/__init__.py +104 -0
- emdash_core/core/config.py +454 -0
- emdash_core/core/exceptions.py +55 -0
- emdash_core/core/models.py +265 -0
- emdash_core/core/review_config.py +57 -0
- emdash_core/db/__init__.py +67 -0
- emdash_core/db/auth.py +134 -0
- emdash_core/db/models.py +91 -0
- emdash_core/db/provider.py +222 -0
- emdash_core/db/providers/__init__.py +5 -0
- emdash_core/db/providers/supabase.py +452 -0
- emdash_core/embeddings/__init__.py +24 -0
- emdash_core/embeddings/indexer.py +534 -0
- emdash_core/embeddings/models.py +192 -0
- emdash_core/embeddings/providers/__init__.py +7 -0
- emdash_core/embeddings/providers/base.py +112 -0
- emdash_core/embeddings/providers/fireworks.py +141 -0
- emdash_core/embeddings/providers/openai.py +104 -0
- emdash_core/embeddings/registry.py +146 -0
- emdash_core/embeddings/service.py +215 -0
- emdash_core/graph/__init__.py +26 -0
- emdash_core/graph/builder.py +134 -0
- emdash_core/graph/connection.py +692 -0
- emdash_core/graph/schema.py +416 -0
- emdash_core/graph/writer.py +667 -0
- emdash_core/ingestion/__init__.py +7 -0
- emdash_core/ingestion/change_detector.py +150 -0
- emdash_core/ingestion/git/__init__.py +5 -0
- emdash_core/ingestion/git/commit_analyzer.py +196 -0
- emdash_core/ingestion/github/__init__.py +6 -0
- emdash_core/ingestion/github/pr_fetcher.py +296 -0
- emdash_core/ingestion/github/task_extractor.py +100 -0
- emdash_core/ingestion/orchestrator.py +540 -0
- emdash_core/ingestion/parsers/__init__.py +10 -0
- emdash_core/ingestion/parsers/base_parser.py +66 -0
- emdash_core/ingestion/parsers/call_graph_builder.py +121 -0
- emdash_core/ingestion/parsers/class_extractor.py +154 -0
- emdash_core/ingestion/parsers/function_extractor.py +202 -0
- emdash_core/ingestion/parsers/import_analyzer.py +119 -0
- emdash_core/ingestion/parsers/python_parser.py +123 -0
- emdash_core/ingestion/parsers/registry.py +72 -0
- emdash_core/ingestion/parsers/ts_ast_parser.js +313 -0
- emdash_core/ingestion/parsers/typescript_parser.py +278 -0
- emdash_core/ingestion/repository.py +346 -0
- emdash_core/models/__init__.py +38 -0
- emdash_core/models/agent.py +68 -0
- emdash_core/models/index.py +77 -0
- emdash_core/models/query.py +113 -0
- emdash_core/planning/__init__.py +7 -0
- emdash_core/planning/agent_api.py +413 -0
- emdash_core/planning/context_builder.py +265 -0
- emdash_core/planning/feature_context.py +232 -0
- emdash_core/planning/feature_expander.py +646 -0
- emdash_core/planning/llm_explainer.py +198 -0
- emdash_core/planning/similarity.py +509 -0
- emdash_core/planning/team_focus.py +821 -0
- emdash_core/server.py +153 -0
- emdash_core/sse/__init__.py +5 -0
- emdash_core/sse/stream.py +196 -0
- emdash_core/swarm/__init__.py +17 -0
- emdash_core/swarm/merge_agent.py +383 -0
- emdash_core/swarm/session_manager.py +274 -0
- emdash_core/swarm/swarm_runner.py +226 -0
- emdash_core/swarm/task_definition.py +137 -0
- emdash_core/swarm/worker_spawner.py +319 -0
- emdash_core/swarm/worktree_manager.py +278 -0
- emdash_core/templates/__init__.py +10 -0
- emdash_core/templates/defaults/agent-builder.md.template +82 -0
- emdash_core/templates/defaults/focus.md.template +115 -0
- emdash_core/templates/defaults/pr-review-enhanced.md.template +309 -0
- emdash_core/templates/defaults/pr-review.md.template +80 -0
- emdash_core/templates/defaults/project.md.template +85 -0
- emdash_core/templates/defaults/research_critic.md.template +112 -0
- emdash_core/templates/defaults/research_planner.md.template +85 -0
- emdash_core/templates/defaults/research_synthesizer.md.template +128 -0
- emdash_core/templates/defaults/reviewer.md.template +81 -0
- emdash_core/templates/defaults/spec.md.template +41 -0
- emdash_core/templates/defaults/tasks.md.template +78 -0
- emdash_core/templates/loader.py +296 -0
- emdash_core/utils/__init__.py +45 -0
- emdash_core/utils/git.py +84 -0
- emdash_core/utils/image.py +502 -0
- emdash_core/utils/logger.py +51 -0
- emdash_core-0.1.7.dist-info/METADATA +35 -0
- emdash_core-0.1.7.dist-info/RECORD +187 -0
- emdash_core-0.1.7.dist-info/WHEEL +4 -0
- 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
|