codegraph-engine 3.0.0__2-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.
- codegraph/__init__.py +37 -0
- codegraph/agent.py +26 -0
- codegraph/agent_brain.py +2212 -0
- codegraph/agent_capabilities.py +4058 -0
- codegraph/agent_rules.py +531 -0
- codegraph/api_drift.py +455 -0
- codegraph/architecture.py +371 -0
- codegraph/async_queue/__init__.py +50 -0
- codegraph/async_queue/detectors/__init__.py +48 -0
- codegraph/async_queue/detectors/celery.py +160 -0
- codegraph/async_queue/detectors/kafka.py +168 -0
- codegraph/async_queue/detectors/rabbitmq.py +116 -0
- codegraph/async_queue/detectors/redis.py +130 -0
- codegraph/async_queue/detectors/sqs.py +135 -0
- codegraph/async_queue/models.py +161 -0
- codegraph/async_queue/otel_models.py +74 -0
- codegraph/async_queue/otel_parser.py +337 -0
- codegraph/async_queue/reconciler.py +427 -0
- codegraph/async_queue/scanner.py +291 -0
- codegraph/async_queue/traversal.py +447 -0
- codegraph/audit.py +106 -0
- codegraph/binding_resolver.py +616 -0
- codegraph/branch_comparison.py +197 -0
- codegraph/cache.py +95 -0
- codegraph/change_impact.py +547 -0
- codegraph/cli.py +2436 -0
- codegraph/cli_output.py +205 -0
- codegraph/config.py +45 -0
- codegraph/constraints.py +238 -0
- codegraph/context.py +2182 -0
- codegraph/context_freshness.py +351 -0
- codegraph/database/__init__.py +72 -0
- codegraph/database/extractor.py +3651 -0
- codegraph/database/interrogation.py +1494 -0
- codegraph/database/models.py +310 -0
- codegraph/database/schema_drift.py +862 -0
- codegraph/epistemic.py +200 -0
- codegraph/errors.py +282 -0
- codegraph/evidence/__init__.py +15 -0
- codegraph/evidence/citations.py +418 -0
- codegraph/evidence_contract.py +486 -0
- codegraph/frameworks.py +960 -0
- codegraph/frameworks_nestjs.py +502 -0
- codegraph/frameworks_nextjs.py +337 -0
- codegraph/frameworks_react.py +457 -0
- codegraph/freshness.py +335 -0
- codegraph/frontend_html.py +109 -0
- codegraph/git.py +362 -0
- codegraph/git_state.py +545 -0
- codegraph/graph/__init__.py +46 -0
- codegraph/graph/models.py +56 -0
- codegraph/graph/traversal.py +1993 -0
- codegraph/indexing/__init__.py +24 -0
- codegraph/indexing/classifier.py +564 -0
- codegraph/indexing/indexer.py +2578 -0
- codegraph/indexing/models.py +427 -0
- codegraph/indexing/parser.py +3404 -0
- codegraph/indexing/pool.py +136 -0
- codegraph/indexing/scanner.py +211 -0
- codegraph/indexing/telemetry.py +326 -0
- codegraph/indexing/templates.py +418 -0
- codegraph/indexing/test_framework.py +116 -0
- codegraph/installer.py +1702 -0
- codegraph/interrogation.py +2355 -0
- codegraph/llm/__init__.py +3 -0
- codegraph/llm/base.py +15 -0
- codegraph/llm/context.py +20 -0
- codegraph/mcp/__init__.py +3 -0
- codegraph/mcp/server.py +1476 -0
- codegraph/mcp_diagnostics.py +882 -0
- codegraph/memory/__init__.py +3 -0
- codegraph/memory/store.py +46 -0
- codegraph/models.py +310 -0
- codegraph/monorepo.py +925 -0
- codegraph/observability.py +262 -0
- codegraph/optimizer.py +1014 -0
- codegraph/planner.py +417 -0
- codegraph/process_lifecycle.py +1153 -0
- codegraph/py.typed +1 -0
- codegraph/query_expansion.py +199 -0
- codegraph/ranking.py +408 -0
- codegraph/refactor/__init__.py +36 -0
- codegraph/refactor/models.py +114 -0
- codegraph/refactor/renamer.py +844 -0
- codegraph/refactor/transactions.py +414 -0
- codegraph/resolver.py +3968 -0
- codegraph/resources/__init__.py +46 -0
- codegraph/resources/cache.py +117 -0
- codegraph/resources/coalescer.py +83 -0
- codegraph/resources/debouncer.py +98 -0
- codegraph/resources/governor.py +219 -0
- codegraph/resources/monitor.py +102 -0
- codegraph/resources/policy.py +123 -0
- codegraph/retrieval_policy.py +320 -0
- codegraph/roblox.py +2174 -0
- codegraph/route_composer.py +363 -0
- codegraph/route_topology.py +172 -0
- codegraph/runtime/__init__.py +51 -0
- codegraph/runtime/collector.py +155 -0
- codegraph/runtime/db_watcher.py +191 -0
- codegraph/runtime/ingestor.py +692 -0
- codegraph/runtime/models.py +130 -0
- codegraph/runtime/port_inspector.py +304 -0
- codegraph/runtime/reconciliation.py +492 -0
- codegraph/runtime/runner.py +353 -0
- codegraph/runtime/trace_linker.py +175 -0
- codegraph/search/__init__.py +25 -0
- codegraph/search/hybrid.py +846 -0
- codegraph/search/semantic.py +28 -0
- codegraph/security/__init__.py +51 -0
- codegraph/security/paths.py +189 -0
- codegraph/security/redaction.py +633 -0
- codegraph/semantic_conflicts.py +270 -0
- codegraph/semantic_decorators.py +372 -0
- codegraph/structural_diff.py +542 -0
- codegraph/styles.py +144 -0
- codegraph/symbol_history.py +277 -0
- codegraph/target_resolver.py +348 -0
- codegraph/task.py +637 -0
- codegraph/tool_selection_eval.py +1158 -0
- codegraph/tsconfig.py +236 -0
- codegraph/ui/__init__.py +6 -0
- codegraph/ui/server.py +755 -0
- codegraph/watcher.py +323 -0
- codegraph_engine-3.0.0.dist-info/METADATA +868 -0
- codegraph_engine-3.0.0.dist-info/RECORD +130 -0
- codegraph_engine-3.0.0.dist-info/WHEEL +6 -0
- codegraph_engine-3.0.0.dist-info/entry_points.txt +2 -0
- codegraph_engine-3.0.0.dist-info/licenses/LICENSE +21 -0
- codegraph_engine-3.0.0.dist-info/top_level.txt +1 -0
codegraph/context.py
ADDED
|
@@ -0,0 +1,2182 @@
|
|
|
1
|
+
"""Context Compiler — flag-ship orchestration engine for CodeGraph MCP.
|
|
2
|
+
|
|
3
|
+
Translates TaskSpec and RetrievalPlan into a verified, bounded, coverage-aware,
|
|
4
|
+
and deduplicated ContextPacket with full provenance and zero LLM API dependency.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import concurrent.futures
|
|
9
|
+
import os
|
|
10
|
+
import sqlite3
|
|
11
|
+
import time
|
|
12
|
+
from dataclasses import field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Literal
|
|
15
|
+
|
|
16
|
+
from pydantic import BaseModel
|
|
17
|
+
|
|
18
|
+
from codegraph.architecture import get_architecture
|
|
19
|
+
from codegraph.cache import (
|
|
20
|
+
compute_context_cache_key,
|
|
21
|
+
get_cached_context_packet,
|
|
22
|
+
store_cached_context_packet,
|
|
23
|
+
)
|
|
24
|
+
from codegraph.constraints import ConstraintGuard
|
|
25
|
+
from codegraph.freshness import (
|
|
26
|
+
FreshnessStatus,
|
|
27
|
+
check_freshness,
|
|
28
|
+
index_generation,
|
|
29
|
+
)
|
|
30
|
+
from codegraph.git import changed_files, is_git_repository, recent_commits
|
|
31
|
+
from codegraph.graph import GraphSeedPolicy
|
|
32
|
+
from codegraph.graph.traversal import (
|
|
33
|
+
find_callees,
|
|
34
|
+
find_callers,
|
|
35
|
+
find_parallel_implementations,
|
|
36
|
+
find_related_tests,
|
|
37
|
+
)
|
|
38
|
+
from codegraph.observability import (
|
|
39
|
+
ExecutionMetadata,
|
|
40
|
+
ExecutionTiming,
|
|
41
|
+
OperationMetric,
|
|
42
|
+
Timer,
|
|
43
|
+
create_request_id,
|
|
44
|
+
get_global_metrics,
|
|
45
|
+
)
|
|
46
|
+
from codegraph.optimizer import (
|
|
47
|
+
HARD_MAX_LINES,
|
|
48
|
+
CandidateContextItem,
|
|
49
|
+
ContextBudgetSpec,
|
|
50
|
+
compress_relationships,
|
|
51
|
+
optimize_context_budget,
|
|
52
|
+
select_bounded_snippet,
|
|
53
|
+
)
|
|
54
|
+
from codegraph.planner import RetrievalPlan, build_retrieval_plan
|
|
55
|
+
from codegraph.query_expansion import QueryExpansion, expand_query_terms, get_search_queries
|
|
56
|
+
from codegraph.ranking import RankedItem, rank
|
|
57
|
+
from codegraph.resources import (
|
|
58
|
+
TaskPriority,
|
|
59
|
+
get_global_coalescer,
|
|
60
|
+
get_global_governor,
|
|
61
|
+
get_process_memory_mb,
|
|
62
|
+
)
|
|
63
|
+
from codegraph.retrieval_policy import RetrievalPolicy, get_retrieval_policy
|
|
64
|
+
from codegraph.search import search
|
|
65
|
+
from codegraph.target_resolver import TargetResolution, resolve_targets
|
|
66
|
+
from codegraph.task import (
|
|
67
|
+
TaskSpec,
|
|
68
|
+
canonicalize_intent,
|
|
69
|
+
normalize_task_spec,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
SCHEMA_VERSION = "2.0"
|
|
73
|
+
|
|
74
|
+
Intent = Literal[
|
|
75
|
+
"explain", "debug", "modify", "review", "test", "trace", "impact", "architecture"
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# ContextPacket Models
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class RankingReasonModel(BaseModel):
|
|
85
|
+
code: str
|
|
86
|
+
label: str
|
|
87
|
+
contribution: float
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class SymbolRef(BaseModel):
|
|
91
|
+
symbol: str
|
|
92
|
+
file: str
|
|
93
|
+
kind: str
|
|
94
|
+
start_line: int
|
|
95
|
+
end_line: int
|
|
96
|
+
score: float
|
|
97
|
+
reasons: list[RankingReasonModel] = []
|
|
98
|
+
canonical_id: str | None = None
|
|
99
|
+
reason_code: str = ""
|
|
100
|
+
package_id: str | None = None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class Relationship(BaseModel):
|
|
104
|
+
source: str
|
|
105
|
+
target: str
|
|
106
|
+
relationship: str
|
|
107
|
+
confidence: str
|
|
108
|
+
file: str | None = None
|
|
109
|
+
start_line: int | None = None
|
|
110
|
+
evidence: str = ""
|
|
111
|
+
status: str = "" # FACT | ASSUMPTION | INFERENCE | UNKNOWN | CONFLICT
|
|
112
|
+
evidence_class: str = ""
|
|
113
|
+
supporting_locations: list[str] = []
|
|
114
|
+
occurrence_count: int = 1
|
|
115
|
+
|
|
116
|
+
def model_post_init(self, __context: Any) -> None:
|
|
117
|
+
from codegraph.evidence_contract import validate_relationship_record
|
|
118
|
+
|
|
119
|
+
validated = validate_relationship_record(self)
|
|
120
|
+
object.__setattr__(self, "relationship", validated.relationship)
|
|
121
|
+
object.__setattr__(self, "evidence_class", validated.evidence_class)
|
|
122
|
+
object.__setattr__(self, "confidence", validated.confidence)
|
|
123
|
+
object.__setattr__(self, "status", validated.status)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class TestRef(BaseModel):
|
|
127
|
+
file: str
|
|
128
|
+
symbol: str | None
|
|
129
|
+
relationship: str
|
|
130
|
+
confidence: str
|
|
131
|
+
evidence: str
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class FileRef(BaseModel):
|
|
135
|
+
file: str
|
|
136
|
+
score: float
|
|
137
|
+
reasons: list[RankingReasonModel] = []
|
|
138
|
+
snippet: str
|
|
139
|
+
token_estimate: int
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class EvidenceRef(BaseModel):
|
|
143
|
+
file: str
|
|
144
|
+
start_line: int
|
|
145
|
+
end_line: int
|
|
146
|
+
symbol: str | None
|
|
147
|
+
snippet: str
|
|
148
|
+
confidence: str
|
|
149
|
+
source_hash: str | None = None
|
|
150
|
+
evidence_status: str = "current" # current | stale
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class ContextPacket(BaseModel):
|
|
154
|
+
schema_version: str = SCHEMA_VERSION
|
|
155
|
+
task: str
|
|
156
|
+
intent: str | None
|
|
157
|
+
repository: str
|
|
158
|
+
indexed_commit: str | None
|
|
159
|
+
current_commit: str | None
|
|
160
|
+
freshness: str # FRESH | STALE | PARTIALLY_STALE | UNKNOWN
|
|
161
|
+
freshness_detail: str
|
|
162
|
+
|
|
163
|
+
# Production intelligence extensions
|
|
164
|
+
repository_generation: int = 0
|
|
165
|
+
task_spec: dict[str, object] | None = None
|
|
166
|
+
task_fingerprint: str = ""
|
|
167
|
+
budget: dict[str, object] | None = None
|
|
168
|
+
execution: dict[str, object] | None = None
|
|
169
|
+
mode: str = "BALANCED" # FAST | BALANCED | DEEP
|
|
170
|
+
entry_points: list[dict[str, object]] = []
|
|
171
|
+
framework_facts: list[dict[str, object]] = []
|
|
172
|
+
architecture: list[dict[str, object]] = []
|
|
173
|
+
git_changes: list[dict[str, object]] = []
|
|
174
|
+
git_facts: list[dict[str, object]] = []
|
|
175
|
+
assumptions: list[str] = []
|
|
176
|
+
unknowns: list[str] = []
|
|
177
|
+
conflicts: list[str] = []
|
|
178
|
+
|
|
179
|
+
# Phase 10 & Phase 14 structured ContextPacket sections
|
|
180
|
+
metadata: dict[str, object] = {}
|
|
181
|
+
target: dict[str, object] = {}
|
|
182
|
+
routes: list[dict[str, object]] = []
|
|
183
|
+
packages: list[dict[str, object]] = []
|
|
184
|
+
git_impact: dict[str, object] = {}
|
|
185
|
+
findings: list[dict[str, object]] = []
|
|
186
|
+
coverage: dict[str, object] = {}
|
|
187
|
+
database: list[dict[str, object]] = []
|
|
188
|
+
runtime: list[dict[str, object]] = []
|
|
189
|
+
reconciliation: list[dict[str, object]] = []
|
|
190
|
+
|
|
191
|
+
# Knowledge
|
|
192
|
+
facts: list[dict[str, object]] = field(default_factory=list)
|
|
193
|
+
symbols: list[SymbolRef] = []
|
|
194
|
+
relationships: list[Relationship] = []
|
|
195
|
+
tests: list[TestRef] = []
|
|
196
|
+
files: list[FileRef] = []
|
|
197
|
+
evidence: list[EvidenceRef] = []
|
|
198
|
+
uncertainties: list[str] = []
|
|
199
|
+
|
|
200
|
+
# Token budget & explicit boundary metadata
|
|
201
|
+
candidate_tokens: int = 0
|
|
202
|
+
selected_tokens: int = 0
|
|
203
|
+
selected_lines: int = 0
|
|
204
|
+
coverage_score: float = 0.0
|
|
205
|
+
truncated: bool = False
|
|
206
|
+
candidate_token_estimate: int = 0
|
|
207
|
+
selected_token_estimate: int = 0
|
|
208
|
+
context_reduction_pct: float = 0.0
|
|
209
|
+
selected_files: list[str] = []
|
|
210
|
+
discarded_files: list[str] = []
|
|
211
|
+
|
|
212
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
213
|
+
|
|
214
|
+
def as_dict(self) -> dict[str, object]:
|
|
215
|
+
return self.model_dump()
|
|
216
|
+
|
|
217
|
+
def to_dict(self) -> dict[str, object]:
|
|
218
|
+
return self.model_dump()
|
|
219
|
+
|
|
220
|
+
def format_human(self) -> str:
|
|
221
|
+
"""Format a clean, concise, token-efficient human/agent context summary."""
|
|
222
|
+
lines = [
|
|
223
|
+
f"# Context for Task: {self.task}",
|
|
224
|
+
f"**Intent**: {self.intent or 'UNDERSTAND'} | **Freshness**: {self.freshness} | **Selected Tokens**: ~{self.selected_tokens}",
|
|
225
|
+
"",
|
|
226
|
+
]
|
|
227
|
+
if self.symbols:
|
|
228
|
+
lines.append("## Key Symbols")
|
|
229
|
+
for s in self.symbols[:8]:
|
|
230
|
+
loc = f"{s.file}:{s.start_line}-{s.end_line}" if s.start_line else s.file
|
|
231
|
+
lines.append(f"- **{s.symbol}** (`{s.kind}`) at `{loc}`")
|
|
232
|
+
lines.append("")
|
|
233
|
+
|
|
234
|
+
if self.evidence:
|
|
235
|
+
lines.append("## Targeted Code Evidence")
|
|
236
|
+
for ev in self.evidence[:5]:
|
|
237
|
+
lines.append(f"### {ev.file}:{ev.start_line}-{ev.end_line} ({ev.symbol or 'code'})")
|
|
238
|
+
lines.append("```")
|
|
239
|
+
lines.append(ev.snippet.strip())
|
|
240
|
+
lines.append("```")
|
|
241
|
+
lines.append("")
|
|
242
|
+
|
|
243
|
+
if self.relationships:
|
|
244
|
+
lines.append("## Verified Relationships")
|
|
245
|
+
for r in self.relationships[:8]:
|
|
246
|
+
lines.append(f"- `{r.source}` **{r.relationship}** `{r.target}` [{r.confidence}]")
|
|
247
|
+
lines.append("")
|
|
248
|
+
|
|
249
|
+
if self.routes:
|
|
250
|
+
lines.append("## Relevant Routes")
|
|
251
|
+
for rt in self.routes[:5]:
|
|
252
|
+
method = rt.get("method") or rt.get("http_method") or "ROUTE"
|
|
253
|
+
path = rt.get("path") or rt.get("route_path") or ""
|
|
254
|
+
handler = rt.get("handler") or rt.get("handler_name") or ""
|
|
255
|
+
lines.append(f"- `{method} {path}` -> `{handler}`")
|
|
256
|
+
lines.append("")
|
|
257
|
+
|
|
258
|
+
return "\n".join(lines).strip()
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# ---------------------------------------------------------------------------
|
|
262
|
+
# Token estimation helper
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
_CHARS_PER_TOKEN = 4
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _token_estimate(text: str) -> int:
|
|
269
|
+
return max(1, len(text) // _CHARS_PER_TOKEN)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _resolve_task_or_query_input(
|
|
273
|
+
task: str | TaskSpec | dict[str, Any] | None = "",
|
|
274
|
+
query: str | TaskSpec | dict[str, Any] | None = "",
|
|
275
|
+
) -> str | TaskSpec | dict[str, Any]:
|
|
276
|
+
"""Resolve canonical `query` / `task` inputs with strict conflict validation."""
|
|
277
|
+
def _is_nonempty(val: Any) -> bool:
|
|
278
|
+
if val is None:
|
|
279
|
+
return False
|
|
280
|
+
if isinstance(val, str):
|
|
281
|
+
return bool(val.strip())
|
|
282
|
+
if isinstance(val, dict):
|
|
283
|
+
return bool(val)
|
|
284
|
+
return True
|
|
285
|
+
|
|
286
|
+
has_task = _is_nonempty(task)
|
|
287
|
+
has_query = _is_nonempty(query)
|
|
288
|
+
|
|
289
|
+
if not has_task and not has_query:
|
|
290
|
+
raise ValueError("Either 'query' or 'task' must be provided to get_context (non-empty string or TaskSpec).")
|
|
291
|
+
|
|
292
|
+
if has_task and has_query:
|
|
293
|
+
if isinstance(task, str) and isinstance(query, str):
|
|
294
|
+
if task.strip() != query.strip():
|
|
295
|
+
raise ValueError(
|
|
296
|
+
f"Conflicting 'query' ({query.strip()!r}) and 'task' ({task.strip()!r}) provided; "
|
|
297
|
+
"pass one or identical values."
|
|
298
|
+
)
|
|
299
|
+
return query.strip()
|
|
300
|
+
if isinstance(task, dict) and isinstance(query, str):
|
|
301
|
+
dict_prompt = str(task.get("raw_prompt") or task.get("goal") or task.get("task") or task.get("query") or "").strip()
|
|
302
|
+
if dict_prompt and dict_prompt != query.strip():
|
|
303
|
+
raise ValueError(
|
|
304
|
+
f"Conflicting 'query' ({query.strip()!r}) and 'task' prompt ({dict_prompt!r}) provided."
|
|
305
|
+
)
|
|
306
|
+
if not dict_prompt:
|
|
307
|
+
merged = dict(task)
|
|
308
|
+
merged["raw_prompt"] = query.strip()
|
|
309
|
+
return merged
|
|
310
|
+
return task
|
|
311
|
+
if isinstance(query, dict) and isinstance(task, str):
|
|
312
|
+
dict_prompt = str(query.get("raw_prompt") or query.get("goal") or query.get("task") or query.get("query") or "").strip()
|
|
313
|
+
if dict_prompt and dict_prompt != task.strip():
|
|
314
|
+
raise ValueError(
|
|
315
|
+
f"Conflicting 'query' prompt ({dict_prompt!r}) and 'task' ({task.strip()!r}) provided."
|
|
316
|
+
)
|
|
317
|
+
if not dict_prompt:
|
|
318
|
+
merged = dict(query)
|
|
319
|
+
merged["raw_prompt"] = task.strip()
|
|
320
|
+
return merged
|
|
321
|
+
return query
|
|
322
|
+
if task != query:
|
|
323
|
+
raise ValueError("Conflicting 'query' and 'task' arguments provided to get_context.")
|
|
324
|
+
return task # type: ignore[return-value]
|
|
325
|
+
|
|
326
|
+
resolved = query if has_query else task
|
|
327
|
+
if isinstance(resolved, str):
|
|
328
|
+
return resolved.strip()
|
|
329
|
+
assert resolved is not None
|
|
330
|
+
return resolved
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
# ---------------------------------------------------------------------------
|
|
334
|
+
# Main Context Compiler
|
|
335
|
+
# ---------------------------------------------------------------------------
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def get_context(
|
|
339
|
+
con: sqlite3.Connection,
|
|
340
|
+
repository: Path,
|
|
341
|
+
task: str | TaskSpec | dict[str, Any] = "",
|
|
342
|
+
intent: Intent | str | None = None,
|
|
343
|
+
max_tokens: int = 20_000,
|
|
344
|
+
top_k: int = 25,
|
|
345
|
+
plan: RetrievalPlan | dict[str, Any] | None = None,
|
|
346
|
+
mode: str = "BALANCED", # FAST | BALANCED | DEEP
|
|
347
|
+
explain: bool = False,
|
|
348
|
+
include_generated: bool = False,
|
|
349
|
+
include_vendor: bool = False,
|
|
350
|
+
include_bundles: bool = False,
|
|
351
|
+
include_build_artifacts: bool = False,
|
|
352
|
+
max_symbols: int = 25,
|
|
353
|
+
max_relationships: int = 30,
|
|
354
|
+
max_files: int = 25,
|
|
355
|
+
max_lines: int = 500,
|
|
356
|
+
max_depth: int | None = None,
|
|
357
|
+
max_package_depth: int | None = None,
|
|
358
|
+
query: str | TaskSpec | dict[str, Any] = "",
|
|
359
|
+
) -> ContextPacket:
|
|
360
|
+
"""Compile a deterministic, verified ContextPacket for the given query/task and intent."""
|
|
361
|
+
effective_task = _resolve_task_or_query_input(task=task, query=query)
|
|
362
|
+
if max_tokens <= 0 or max_files <= 0 or max_lines <= 0:
|
|
363
|
+
raise ValueError("max_tokens, max_files, and max_lines must be positive integers (> 0).")
|
|
364
|
+
governor = get_global_governor()
|
|
365
|
+
coalescer = get_global_coalescer()
|
|
366
|
+
flight_key = (
|
|
367
|
+
f"{repository}:{effective_task}:{intent}:{max_tokens}:{top_k}:{mode}:{explain}:"
|
|
368
|
+
f"{include_generated}:{include_vendor}:{include_bundles}:{include_build_artifacts}:"
|
|
369
|
+
f"{max_symbols}:{max_relationships}:{max_files}:{max_lines}:{max_depth}:{max_package_depth}"
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
def _execute() -> ContextPacket:
|
|
373
|
+
with governor.task_scope(TaskPriority.INTERACTIVE_HIGH):
|
|
374
|
+
return _get_context_impl(
|
|
375
|
+
con=con,
|
|
376
|
+
repository=repository,
|
|
377
|
+
task=effective_task,
|
|
378
|
+
intent=intent,
|
|
379
|
+
max_tokens=max_tokens,
|
|
380
|
+
top_k=top_k,
|
|
381
|
+
plan=plan,
|
|
382
|
+
mode=mode,
|
|
383
|
+
explain=explain,
|
|
384
|
+
governor=governor,
|
|
385
|
+
include_generated=include_generated,
|
|
386
|
+
include_vendor=include_vendor,
|
|
387
|
+
include_bundles=include_bundles,
|
|
388
|
+
include_build_artifacts=include_build_artifacts,
|
|
389
|
+
max_symbols=max_symbols,
|
|
390
|
+
max_relationships=max_relationships,
|
|
391
|
+
max_files=max_files,
|
|
392
|
+
max_lines=max_lines,
|
|
393
|
+
max_depth=max_depth,
|
|
394
|
+
max_package_depth=max_package_depth,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
return coalescer.coalesce(flight_key, _execute)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _get_context_impl(
|
|
401
|
+
con: sqlite3.Connection,
|
|
402
|
+
repository: Path,
|
|
403
|
+
task: str | TaskSpec | dict[str, Any],
|
|
404
|
+
intent: Intent | str | None = None,
|
|
405
|
+
max_tokens: int = 20_000,
|
|
406
|
+
top_k: int = 25,
|
|
407
|
+
plan: RetrievalPlan | dict[str, Any] | None = None,
|
|
408
|
+
mode: str = "BALANCED", # FAST | BALANCED | DEEP
|
|
409
|
+
explain: bool = False,
|
|
410
|
+
governor: Any = None,
|
|
411
|
+
include_generated: bool = False,
|
|
412
|
+
include_vendor: bool = False,
|
|
413
|
+
include_bundles: bool = False,
|
|
414
|
+
include_build_artifacts: bool = False,
|
|
415
|
+
max_symbols: int = 25,
|
|
416
|
+
max_relationships: int = 30,
|
|
417
|
+
max_files: int = 25,
|
|
418
|
+
max_lines: int = 500,
|
|
419
|
+
max_depth: int | None = None,
|
|
420
|
+
max_package_depth: int | None = None,
|
|
421
|
+
) -> ContextPacket:
|
|
422
|
+
if governor is None:
|
|
423
|
+
governor = get_global_governor()
|
|
424
|
+
req_id = create_request_id()
|
|
425
|
+
timing = ExecutionTiming()
|
|
426
|
+
with Timer() as timer:
|
|
427
|
+
# 0. Latency Mode configuration
|
|
428
|
+
mode_upper = (mode or "BALANCED").upper()
|
|
429
|
+
if mode_upper not in ("FAST", "BALANCED", "DEEP"):
|
|
430
|
+
mode_upper = "BALANCED"
|
|
431
|
+
|
|
432
|
+
if mode_upper == "FAST":
|
|
433
|
+
effective_top_k = min(top_k, 5)
|
|
434
|
+
max_graph_syms = 2
|
|
435
|
+
max_graph_results = 5
|
|
436
|
+
max_test_syms = 2
|
|
437
|
+
max_test_results = 3
|
|
438
|
+
git_commits_n = 2
|
|
439
|
+
elif mode_upper == "DEEP":
|
|
440
|
+
effective_top_k = max(top_k, 25)
|
|
441
|
+
max_graph_syms = 10
|
|
442
|
+
max_graph_results = 15
|
|
443
|
+
max_test_syms = 8
|
|
444
|
+
max_test_results = 8
|
|
445
|
+
git_commits_n = 10
|
|
446
|
+
else: # BALANCED
|
|
447
|
+
effective_top_k = top_k
|
|
448
|
+
max_graph_syms = 6
|
|
449
|
+
max_graph_results = 10
|
|
450
|
+
max_test_syms = 4
|
|
451
|
+
max_test_results = 5
|
|
452
|
+
git_commits_n = 5
|
|
453
|
+
|
|
454
|
+
# 1. Planning stage
|
|
455
|
+
t_plan_start = time.perf_counter()
|
|
456
|
+
task_spec, ambiguities = normalize_task_spec(task, con)
|
|
457
|
+
|
|
458
|
+
caller_raw_intent = intent
|
|
459
|
+
if caller_raw_intent:
|
|
460
|
+
canonical_intent = canonicalize_intent(str(caller_raw_intent))
|
|
461
|
+
task_spec = TaskSpec(
|
|
462
|
+
schema_version=task_spec.schema_version,
|
|
463
|
+
raw_prompt=task_spec.raw_prompt,
|
|
464
|
+
intent=canonical_intent.value,
|
|
465
|
+
goal=task_spec.goal,
|
|
466
|
+
targets=task_spec.targets,
|
|
467
|
+
entities=task_spec.entities,
|
|
468
|
+
operations=task_spec.operations,
|
|
469
|
+
constraints=task_spec.constraints,
|
|
470
|
+
exclusions=task_spec.exclusions,
|
|
471
|
+
scope_paths=task_spec.scope_paths,
|
|
472
|
+
scope_modules=task_spec.scope_modules,
|
|
473
|
+
frameworks=task_spec.frameworks,
|
|
474
|
+
time_scope=task_spec.time_scope,
|
|
475
|
+
priority_targets=task_spec.priority_targets,
|
|
476
|
+
ambiguities=task_spec.ambiguities,
|
|
477
|
+
assumptions=task_spec.assumptions,
|
|
478
|
+
unknowns=task_spec.unknowns,
|
|
479
|
+
confidence=task_spec.confidence,
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
task_display_str = (
|
|
483
|
+
task if isinstance(task, str) else (task_spec.raw_prompt or task_spec.goal or "task")
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
gen = index_generation(con)
|
|
487
|
+
runtime_edge_count = 0
|
|
488
|
+
try:
|
|
489
|
+
row_rt = con.execute("SELECT count(*), COALESCE(MAX(runtime_generation), 0) FROM runtime_edges").fetchone()
|
|
490
|
+
if row_rt:
|
|
491
|
+
runtime_edge_count = int(row_rt[0] or 0) + int(row_rt[1] or 0) * 1000
|
|
492
|
+
except Exception:
|
|
493
|
+
pass
|
|
494
|
+
cache_key = compute_context_cache_key(
|
|
495
|
+
task_spec=task_spec,
|
|
496
|
+
repository_generation=gen,
|
|
497
|
+
max_tokens=max_tokens,
|
|
498
|
+
extra_config=(
|
|
499
|
+
f"{caller_raw_intent or ''}:{mode_upper}:{top_k}:"
|
|
500
|
+
f"{include_generated}:{include_vendor}:{include_bundles}:{include_build_artifacts}:"
|
|
501
|
+
f"{max_symbols}:{max_relationships}:{max_files}:{max_lines}:{max_depth}:{max_package_depth}:{runtime_edge_count}"
|
|
502
|
+
),
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
# 2. Check Cache
|
|
506
|
+
cached_data = get_cached_context_packet(con, cache_key)
|
|
507
|
+
if cached_data is not None:
|
|
508
|
+
try:
|
|
509
|
+
packet = ContextPacket.model_validate(cached_data)
|
|
510
|
+
timing.total_ms = timer.elapsed_ms
|
|
511
|
+
exec_meta = ExecutionMetadata(
|
|
512
|
+
latency_ms=timer.elapsed_ms,
|
|
513
|
+
cache_hit=True,
|
|
514
|
+
index_generation=gen,
|
|
515
|
+
candidate_count=packet.candidate_token_estimate,
|
|
516
|
+
selected_count=packet.selected_token_estimate,
|
|
517
|
+
mode=mode_upper,
|
|
518
|
+
timing=timing,
|
|
519
|
+
)
|
|
520
|
+
old_exec = cached_data.get("execution", {}) if isinstance(cached_data, dict) else {}
|
|
521
|
+
packet.execution = exec_meta.as_dict()
|
|
522
|
+
packet.execution["excluded_count"] = old_exec.get("excluded_count", 0)
|
|
523
|
+
packet.execution["excluded_artifact_classes"] = old_exec.get("excluded_artifact_classes", [])
|
|
524
|
+
packet.execution["artifact_inclusions"] = old_exec.get("artifact_inclusions", [])
|
|
525
|
+
packet.execution["package_context"] = old_exec.get("package_context")
|
|
526
|
+
packet.execution["tool_efficiency"] = old_exec.get("tool_efficiency", {"tool_calls": 1, "cache_hit": True})
|
|
527
|
+
packet.execution["resource"] = {
|
|
528
|
+
"profile": governor.policy.profile.value,
|
|
529
|
+
"pressure": governor.get_pressure().value,
|
|
530
|
+
"activity": governor.get_activity_mode().value,
|
|
531
|
+
"estimated_memory_mb": get_process_memory_mb(),
|
|
532
|
+
}
|
|
533
|
+
packet.mode = mode_upper
|
|
534
|
+
get_global_metrics().record(
|
|
535
|
+
OperationMetric(
|
|
536
|
+
request_id=req_id,
|
|
537
|
+
tool_or_op="get_context",
|
|
538
|
+
latency_ms=timer.elapsed_ms,
|
|
539
|
+
index_generation=gen,
|
|
540
|
+
cache_hit=True,
|
|
541
|
+
candidate_count=packet.candidate_token_estimate,
|
|
542
|
+
selected_count=packet.selected_token_estimate,
|
|
543
|
+
estimated_tokens=packet.selected_token_estimate,
|
|
544
|
+
task_intent=task_spec.intent,
|
|
545
|
+
freshness=packet.freshness,
|
|
546
|
+
)
|
|
547
|
+
)
|
|
548
|
+
return packet
|
|
549
|
+
except Exception:
|
|
550
|
+
pass
|
|
551
|
+
|
|
552
|
+
if plan is None:
|
|
553
|
+
retrieval_plan = build_retrieval_plan(
|
|
554
|
+
task_spec=task_spec,
|
|
555
|
+
ambiguities=ambiguities,
|
|
556
|
+
con=con,
|
|
557
|
+
token_budget=max_tokens,
|
|
558
|
+
)
|
|
559
|
+
elif isinstance(plan, dict):
|
|
560
|
+
retrieval_plan = build_retrieval_plan(
|
|
561
|
+
task_spec=task_spec,
|
|
562
|
+
ambiguities=ambiguities,
|
|
563
|
+
con=con,
|
|
564
|
+
token_budget=int(plan.get("token_budget", max_tokens)),
|
|
565
|
+
)
|
|
566
|
+
else:
|
|
567
|
+
retrieval_plan = plan
|
|
568
|
+
timing.planning_ms = (time.perf_counter() - t_plan_start) * 1000
|
|
569
|
+
|
|
570
|
+
# ── v2.1: First-class target resolution + qualified query expansion ──
|
|
571
|
+
# Resolve every target in the spec against the indexed repository before
|
|
572
|
+
# any retrieval work. This drives qualified, precise search queries and
|
|
573
|
+
# prevents generic method names from polluting search results.
|
|
574
|
+
_raw_targets = list(task_spec.priority_targets or task_spec.targets)
|
|
575
|
+
_target_resolutions: dict[str, TargetResolution] = {}
|
|
576
|
+
_retrieval_policy: RetrievalPolicy = get_retrieval_policy(task_spec.intent)
|
|
577
|
+
if _raw_targets and con:
|
|
578
|
+
_target_resolutions = resolve_targets(_raw_targets, con)
|
|
579
|
+
_expanded_queries_obj: list[QueryExpansion] = expand_query_terms(
|
|
580
|
+
_raw_targets,
|
|
581
|
+
con,
|
|
582
|
+
target_resolutions=_target_resolutions,
|
|
583
|
+
max_expansions=10,
|
|
584
|
+
)
|
|
585
|
+
_v21_queries: list[str] = get_search_queries(_expanded_queries_obj)
|
|
586
|
+
# Merge v2.1 qualified queries with planner queries (prefer qualified)
|
|
587
|
+
_planner_queries: list[str] = [
|
|
588
|
+
q for group in retrieval_plan.query_groups for q in group.queries
|
|
589
|
+
]
|
|
590
|
+
# Deduplicated, qualified-first merge
|
|
591
|
+
_merged_queries: list[str] = []
|
|
592
|
+
_seen_q: set[str] = set()
|
|
593
|
+
for q in _v21_queries + _planner_queries:
|
|
594
|
+
if q and q not in _seen_q:
|
|
595
|
+
_seen_q.add(q)
|
|
596
|
+
_merged_queries.append(q)
|
|
597
|
+
# ────────────────────────────────────────────────────────────────────
|
|
598
|
+
|
|
599
|
+
# 3. Freshness check
|
|
600
|
+
freshness_report = check_freshness(repository, con)
|
|
601
|
+
stale_paths = set(freshness_report.modified_files + freshness_report.deleted_files)
|
|
602
|
+
|
|
603
|
+
# 4. Check whether database is file-backed for multi-threaded parallel read
|
|
604
|
+
db_file_path: str | None = None
|
|
605
|
+
try:
|
|
606
|
+
for r in con.execute("PRAGMA database_list").fetchall():
|
|
607
|
+
if r[1] == "main" and r[2]:
|
|
608
|
+
db_file_path = str(r[2])
|
|
609
|
+
break
|
|
610
|
+
except Exception:
|
|
611
|
+
pass
|
|
612
|
+
is_disk_db = (
|
|
613
|
+
db_file_path is not None
|
|
614
|
+
and Path(db_file_path).is_file()
|
|
615
|
+
and os.name != "nt"
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
queries_to_run: list[str] = _merged_queries if _merged_queries else [
|
|
619
|
+
q for group in retrieval_plan.query_groups for q in group.queries
|
|
620
|
+
]
|
|
621
|
+
|
|
622
|
+
candidate_dicts: list[dict[str, object]] = []
|
|
623
|
+
target_symbols: set[str] = set()
|
|
624
|
+
for t in task_spec.targets:
|
|
625
|
+
target_symbols.add(t)
|
|
626
|
+
target_symbols.add(t.split(".")[-1])
|
|
627
|
+
# Also add canonical IDs from target resolutions as target symbols
|
|
628
|
+
for res in _target_resolutions.values():
|
|
629
|
+
if res.canonical_id:
|
|
630
|
+
target_symbols.add(res.canonical_id)
|
|
631
|
+
target_symbols.add(res.canonical_id.split(".")[-1])
|
|
632
|
+
if res.qualified_name:
|
|
633
|
+
target_symbols.add(res.qualified_name)
|
|
634
|
+
|
|
635
|
+
# Parallel / Sequential retrieval worker helpers
|
|
636
|
+
def _exec_search(path: str | None, queries: list[str]) -> list[dict[str, object]]:
|
|
637
|
+
items: list[dict[str, object]] = []
|
|
638
|
+
if path:
|
|
639
|
+
c = sqlite3.connect(str(path), timeout=15.0)
|
|
640
|
+
c.row_factory = sqlite3.Row
|
|
641
|
+
c.execute("PRAGMA busy_timeout = 15000")
|
|
642
|
+
c.execute("PRAGMA query_only = ON")
|
|
643
|
+
try:
|
|
644
|
+
for query in queries:
|
|
645
|
+
for item in search(
|
|
646
|
+
c,
|
|
647
|
+
query,
|
|
648
|
+
top_k=effective_top_k,
|
|
649
|
+
include_generated=include_generated,
|
|
650
|
+
include_vendor=include_vendor,
|
|
651
|
+
include_bundles=include_bundles,
|
|
652
|
+
include_build_artifacts=include_build_artifacts,
|
|
653
|
+
include_text_files=False,
|
|
654
|
+
):
|
|
655
|
+
items.append(item.as_dict())
|
|
656
|
+
finally:
|
|
657
|
+
c.close()
|
|
658
|
+
else:
|
|
659
|
+
for query in queries:
|
|
660
|
+
for item in search(
|
|
661
|
+
con,
|
|
662
|
+
query,
|
|
663
|
+
top_k=effective_top_k,
|
|
664
|
+
include_generated=include_generated,
|
|
665
|
+
include_vendor=include_vendor,
|
|
666
|
+
include_bundles=include_bundles,
|
|
667
|
+
include_build_artifacts=include_build_artifacts,
|
|
668
|
+
include_text_files=False,
|
|
669
|
+
):
|
|
670
|
+
items.append(item.as_dict())
|
|
671
|
+
return items
|
|
672
|
+
|
|
673
|
+
def _exec_routes(path: str | None) -> list[Any]:
|
|
674
|
+
try:
|
|
675
|
+
if path:
|
|
676
|
+
c = sqlite3.connect(str(path), timeout=15.0)
|
|
677
|
+
c.row_factory = sqlite3.Row
|
|
678
|
+
c.execute("PRAGMA busy_timeout = 15000")
|
|
679
|
+
c.execute("PRAGMA query_only = ON")
|
|
680
|
+
try:
|
|
681
|
+
return c.execute(
|
|
682
|
+
"SELECT route_path, http_method, handler_name, file_path, line, endpoint_id FROM framework_routes"
|
|
683
|
+
).fetchall()
|
|
684
|
+
finally:
|
|
685
|
+
c.close()
|
|
686
|
+
else:
|
|
687
|
+
return con.execute(
|
|
688
|
+
"SELECT route_path, http_method, handler_name, file_path, line, endpoint_id FROM framework_routes"
|
|
689
|
+
).fetchall()
|
|
690
|
+
except Exception:
|
|
691
|
+
return []
|
|
692
|
+
|
|
693
|
+
def _exec_git(repo: Path, include_git: bool, commit_limit: int) -> tuple[list[dict[str, Any]], set[str]]:
|
|
694
|
+
changes: list[dict[str, Any]] = []
|
|
695
|
+
recent: set[str] = set()
|
|
696
|
+
try:
|
|
697
|
+
if is_git_repository(repo):
|
|
698
|
+
diffs = changed_files(repo, since="HEAD~10", until="HEAD")
|
|
699
|
+
for d in diffs:
|
|
700
|
+
recent.add(d.path)
|
|
701
|
+
changes.append(d.as_dict())
|
|
702
|
+
if include_git:
|
|
703
|
+
for commit in recent_commits(repo, n=commit_limit):
|
|
704
|
+
changes.append(commit.as_dict())
|
|
705
|
+
except Exception:
|
|
706
|
+
pass
|
|
707
|
+
return changes, recent
|
|
708
|
+
|
|
709
|
+
include_arch = retrieval_plan.include_architecture or (mode_upper == "DEEP")
|
|
710
|
+
def _exec_arch(path: str | None, repo: Path) -> list[dict[str, object]]:
|
|
711
|
+
archs: list[dict[str, object]] = []
|
|
712
|
+
try:
|
|
713
|
+
if path:
|
|
714
|
+
c = sqlite3.connect(str(path), timeout=15.0)
|
|
715
|
+
c.row_factory = sqlite3.Row
|
|
716
|
+
c.execute("PRAGMA busy_timeout = 15000")
|
|
717
|
+
c.execute("PRAGMA query_only = ON")
|
|
718
|
+
try:
|
|
719
|
+
archs.append(get_architecture(c, repo))
|
|
720
|
+
finally:
|
|
721
|
+
c.close()
|
|
722
|
+
else:
|
|
723
|
+
archs.append(get_architecture(con, repo))
|
|
724
|
+
except Exception:
|
|
725
|
+
pass
|
|
726
|
+
return archs
|
|
727
|
+
|
|
728
|
+
# Execute searches, framework routes, git, and architecture
|
|
729
|
+
t_stage2_start = time.perf_counter()
|
|
730
|
+
if is_disk_db:
|
|
731
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=min(4, os.cpu_count() or 4)) as executor:
|
|
732
|
+
f_search = executor.submit(_exec_search, db_file_path, queries_to_run)
|
|
733
|
+
f_routes = executor.submit(_exec_routes, db_file_path)
|
|
734
|
+
f_git = executor.submit(_exec_git, repository, retrieval_plan.include_git, git_commits_n)
|
|
735
|
+
f_arch = executor.submit(_exec_arch, db_file_path, repository) if include_arch else None
|
|
736
|
+
|
|
737
|
+
search_res = f_search.result()
|
|
738
|
+
routes_res = f_routes.result()
|
|
739
|
+
git_changes_out, recent_paths = f_git.result()
|
|
740
|
+
architecture_out = f_arch.result() if f_arch else []
|
|
741
|
+
else:
|
|
742
|
+
search_res = _exec_search(None, queries_to_run)
|
|
743
|
+
routes_res = _exec_routes(None)
|
|
744
|
+
git_changes_out, recent_paths = _exec_git(repository, retrieval_plan.include_git, git_commits_n)
|
|
745
|
+
architecture_out = _exec_arch(None, repository) if include_arch else []
|
|
746
|
+
|
|
747
|
+
stage2_duration = (time.perf_counter() - t_stage2_start) * 1000
|
|
748
|
+
timing.search_ms = stage2_duration * 0.4
|
|
749
|
+
timing.framework_ms = stage2_duration * 0.2
|
|
750
|
+
timing.git_ms = stage2_duration * 0.4
|
|
751
|
+
|
|
752
|
+
for r_dict in search_res:
|
|
753
|
+
candidate_dicts.append(r_dict)
|
|
754
|
+
sym = r_dict.get("symbol")
|
|
755
|
+
if sym and isinstance(sym, str):
|
|
756
|
+
target_symbols.add(sym)
|
|
757
|
+
target_symbols.add(sym.split(".")[-1])
|
|
758
|
+
|
|
759
|
+
stage1_candidate_count = len(candidate_dicts)
|
|
760
|
+
|
|
761
|
+
# Instantiate ConstraintGuard for hard safety invariants
|
|
762
|
+
_guard = ConstraintGuard.from_task_spec(task_spec)
|
|
763
|
+
|
|
764
|
+
# Process framework routes
|
|
765
|
+
route_relationships: list[Relationship] = []
|
|
766
|
+
entry_points_out: list[dict[str, object]] = []
|
|
767
|
+
framework_facts_out: list[dict[str, object]] = []
|
|
768
|
+
|
|
769
|
+
for r in routes_res:
|
|
770
|
+
r_path = r["route_path"]
|
|
771
|
+
r_method = r["http_method"]
|
|
772
|
+
ep_id = r["endpoint_id"] or f"{r_method} {r_path}"
|
|
773
|
+
r_segments = [
|
|
774
|
+
seg.lower()
|
|
775
|
+
for seg in str(r_path).split("/")
|
|
776
|
+
if len(seg) >= 4
|
|
777
|
+
and not seg.startswith("{")
|
|
778
|
+
and not seg.startswith("<")
|
|
779
|
+
and seg.lower() not in {"api", "v1", "v2"}
|
|
780
|
+
]
|
|
781
|
+
matches_task = (
|
|
782
|
+
r_path in task_display_str
|
|
783
|
+
or ep_id.lower() in task_display_str.lower()
|
|
784
|
+
or (r_method in task_display_str.upper() and r_path in task_display_str)
|
|
785
|
+
or task_spec.intent == "ARCHITECTURE"
|
|
786
|
+
or any(seg in task_display_str.lower() for seg in r_segments)
|
|
787
|
+
or any(
|
|
788
|
+
(len(tgt) >= 3 and (tgt.lower() == r["handler_name"].lower() or tgt.lower() in r_path.lower()))
|
|
789
|
+
for tgt in task_spec.targets
|
|
790
|
+
)
|
|
791
|
+
)
|
|
792
|
+
if matches_task and _guard.allows_symbol(str(r["handler_name"])) and _guard.allows_file(str(r["file_path"])):
|
|
793
|
+
target_symbols.add(r["handler_name"])
|
|
794
|
+
r_line = r["line"] or 1
|
|
795
|
+
candidate_dicts.append(
|
|
796
|
+
{
|
|
797
|
+
"file": r["file_path"],
|
|
798
|
+
"symbol": r["handler_name"],
|
|
799
|
+
"start_line": r_line,
|
|
800
|
+
"end_line": r_line,
|
|
801
|
+
"score": 0.95,
|
|
802
|
+
"relationship": "HANDLED_BY",
|
|
803
|
+
"snippet": f"Endpoint: {ep_id} handled by {r['handler_name']}",
|
|
804
|
+
}
|
|
805
|
+
)
|
|
806
|
+
route_rel = Relationship(
|
|
807
|
+
source=ep_id,
|
|
808
|
+
target=r["handler_name"],
|
|
809
|
+
relationship="HANDLED_BY",
|
|
810
|
+
confidence="HIGH",
|
|
811
|
+
file=r["file_path"],
|
|
812
|
+
start_line=r_line,
|
|
813
|
+
evidence=f"Route definition for {ep_id}",
|
|
814
|
+
status="FACT",
|
|
815
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
816
|
+
)
|
|
817
|
+
route_relationships.append(route_rel)
|
|
818
|
+
entry_points_out.append(
|
|
819
|
+
{
|
|
820
|
+
"endpoint_id": ep_id,
|
|
821
|
+
"route_path": r_path,
|
|
822
|
+
"http_method": r_method,
|
|
823
|
+
"handler": r["handler_name"],
|
|
824
|
+
"file": r["file_path"],
|
|
825
|
+
"line": r_line,
|
|
826
|
+
}
|
|
827
|
+
)
|
|
828
|
+
framework_facts_out.append(
|
|
829
|
+
{
|
|
830
|
+
"construct": "ROUTE",
|
|
831
|
+
"endpoint_id": ep_id,
|
|
832
|
+
"handler": r["handler_name"],
|
|
833
|
+
"file": r["file_path"],
|
|
834
|
+
"line": r_line,
|
|
835
|
+
}
|
|
836
|
+
)
|
|
837
|
+
try:
|
|
838
|
+
dec_rows = con.execute(
|
|
839
|
+
"SELECT decorators, canonical_id, qualified_name, start_line FROM symbols "
|
|
840
|
+
"WHERE (name=? OR qualified_name=? OR canonical_id=?) AND path=?",
|
|
841
|
+
(r["handler_name"], r["handler_name"], r["handler_name"], r["file_path"]),
|
|
842
|
+
).fetchall()
|
|
843
|
+
for d_row in dec_rows:
|
|
844
|
+
if d_row["decorators"]:
|
|
845
|
+
for dec in str(d_row["decorators"]).split(","):
|
|
846
|
+
clean_dec = dec.strip()
|
|
847
|
+
if clean_dec:
|
|
848
|
+
fmt_dec = f"@{clean_dec}" if not clean_dec.startswith("@") else clean_dec
|
|
849
|
+
framework_facts_out.append(
|
|
850
|
+
{
|
|
851
|
+
"framework": str(r["framework"] if "framework" in r.keys() else "framework"),
|
|
852
|
+
"decorator": fmt_dec,
|
|
853
|
+
"route": r_path,
|
|
854
|
+
"handler": r["handler_name"],
|
|
855
|
+
"canonical_id": str(d_row["canonical_id"]),
|
|
856
|
+
"file": r["file_path"],
|
|
857
|
+
"line": int(d_row["start_line"]),
|
|
858
|
+
"confidence": "HIGH",
|
|
859
|
+
"evidence": f"Decorator {fmt_dec} on {d_row['qualified_name']}",
|
|
860
|
+
}
|
|
861
|
+
)
|
|
862
|
+
except Exception:
|
|
863
|
+
pass
|
|
864
|
+
|
|
865
|
+
# Seed architectural boundary classes and gateway methods for ARCHITECTURE intent
|
|
866
|
+
if task_spec.intent == "ARCHITECTURE":
|
|
867
|
+
try:
|
|
868
|
+
arch_rows = con.execute(
|
|
869
|
+
"SELECT canonical_id, qualified_name, name, kind, path, start_line, end_line "
|
|
870
|
+
"FROM symbols WHERE kind = 'class' OR name = 'app'"
|
|
871
|
+
).fetchall()
|
|
872
|
+
for a_row in arch_rows:
|
|
873
|
+
a_name = str(a_row["name"])
|
|
874
|
+
a_sym = str(a_row["qualified_name"] or a_name)
|
|
875
|
+
a_cid = str(a_row["canonical_id"] or "")
|
|
876
|
+
a_file = str(a_row["path"] or "")
|
|
877
|
+
if (
|
|
878
|
+
_guard.allows_symbol(a_sym)
|
|
879
|
+
and _guard.allows_symbol(a_name)
|
|
880
|
+
and (not a_cid or _guard.allows_canonical_id(a_cid))
|
|
881
|
+
and _guard.allows_file(a_file)
|
|
882
|
+
):
|
|
883
|
+
target_symbols.add(a_name)
|
|
884
|
+
target_symbols.add(a_sym)
|
|
885
|
+
candidate_dicts.append(
|
|
886
|
+
{
|
|
887
|
+
"file": a_file,
|
|
888
|
+
"symbol": a_name,
|
|
889
|
+
"canonical_id": a_cid,
|
|
890
|
+
"start_line": int(a_row["start_line"] or 1),
|
|
891
|
+
"end_line": int(a_row["end_line"] or 1),
|
|
892
|
+
"score": 0.90,
|
|
893
|
+
"relationship": "DEFINES",
|
|
894
|
+
"snippet": f"Architectural symbol {a_sym} ({a_row['kind']}) in {a_file}",
|
|
895
|
+
}
|
|
896
|
+
)
|
|
897
|
+
if "gateway" in task_display_str.lower():
|
|
898
|
+
gw_rows = con.execute(
|
|
899
|
+
"SELECT canonical_id, qualified_name, name, kind, path, start_line, end_line "
|
|
900
|
+
"FROM symbols WHERE qualified_name LIKE '%Gateway.%'"
|
|
901
|
+
).fetchall()
|
|
902
|
+
for g_row in gw_rows:
|
|
903
|
+
g_name = str(g_row["name"])
|
|
904
|
+
g_sym = str(g_row["qualified_name"] or g_name)
|
|
905
|
+
g_cid = str(g_row["canonical_id"] or "")
|
|
906
|
+
g_file = str(g_row["path"] or "")
|
|
907
|
+
if (
|
|
908
|
+
_guard.allows_symbol(g_sym)
|
|
909
|
+
and _guard.allows_symbol(g_name)
|
|
910
|
+
and (not g_cid or _guard.allows_canonical_id(g_cid))
|
|
911
|
+
and _guard.allows_file(g_file)
|
|
912
|
+
):
|
|
913
|
+
target_symbols.add(g_name)
|
|
914
|
+
target_symbols.add(g_sym)
|
|
915
|
+
candidate_dicts.append(
|
|
916
|
+
{
|
|
917
|
+
"file": g_file,
|
|
918
|
+
"symbol": g_name,
|
|
919
|
+
"canonical_id": g_cid,
|
|
920
|
+
"start_line": int(g_row["start_line"] or 1),
|
|
921
|
+
"end_line": int(g_row["end_line"] or 1),
|
|
922
|
+
"score": 0.88,
|
|
923
|
+
"relationship": "DEFINES",
|
|
924
|
+
"snippet": f"Gateway method {g_sym} in {g_file}",
|
|
925
|
+
}
|
|
926
|
+
)
|
|
927
|
+
except Exception:
|
|
928
|
+
pass
|
|
929
|
+
|
|
930
|
+
# Seed direct methods of resolved class targets
|
|
931
|
+
for res in _target_resolutions.values():
|
|
932
|
+
cls_qname = res.qualified_name
|
|
933
|
+
if cls_qname and res.target_type.value == "CLASS":
|
|
934
|
+
try:
|
|
935
|
+
m_rows = con.execute(
|
|
936
|
+
"SELECT canonical_id, qualified_name, name, kind, path, start_line, end_line "
|
|
937
|
+
"FROM symbols WHERE qualified_name LIKE ?",
|
|
938
|
+
(f"{cls_qname}.%",),
|
|
939
|
+
).fetchall()
|
|
940
|
+
for m_row in m_rows:
|
|
941
|
+
m_name = str(m_row["name"])
|
|
942
|
+
m_sym = str(m_row["qualified_name"] or m_name)
|
|
943
|
+
m_cid = str(m_row["canonical_id"] or "")
|
|
944
|
+
m_file = str(m_row["path"] or "")
|
|
945
|
+
if (
|
|
946
|
+
_guard.allows_symbol(m_sym)
|
|
947
|
+
and _guard.allows_symbol(m_name)
|
|
948
|
+
and (not m_cid or _guard.allows_canonical_id(m_cid))
|
|
949
|
+
and _guard.allows_file(m_file)
|
|
950
|
+
):
|
|
951
|
+
target_symbols.add(m_name)
|
|
952
|
+
target_symbols.add(m_sym)
|
|
953
|
+
candidate_dicts.append(
|
|
954
|
+
{
|
|
955
|
+
"file": m_file,
|
|
956
|
+
"symbol": m_name,
|
|
957
|
+
"canonical_id": m_cid,
|
|
958
|
+
"start_line": int(m_row["start_line"] or 1),
|
|
959
|
+
"end_line": int(m_row["end_line"] or 1),
|
|
960
|
+
"score": 0.88,
|
|
961
|
+
"relationship": "DEFINES",
|
|
962
|
+
"snippet": f"Method {m_sym} of target class {cls_qname}",
|
|
963
|
+
}
|
|
964
|
+
)
|
|
965
|
+
except Exception:
|
|
966
|
+
pass
|
|
967
|
+
|
|
968
|
+
# 5. Graph exploration (callers/callees) & Tests with deterministic seeds
|
|
969
|
+
t_graph_start = time.perf_counter()
|
|
970
|
+
|
|
971
|
+
# Build grounded, deterministic graph roots in priority order:
|
|
972
|
+
# 1. canonical targets from TargetResolver
|
|
973
|
+
# 2. exact qualified targets
|
|
974
|
+
# 3. route handlers
|
|
975
|
+
# 4. priority targets / task targets
|
|
976
|
+
ordered_seeds: list[str] = []
|
|
977
|
+
_seen_seeds: set[str] = set()
|
|
978
|
+
|
|
979
|
+
for res in _target_resolutions.values():
|
|
980
|
+
if res.canonical_id and _guard.allows_canonical_id(res.canonical_id) and res.canonical_id not in _seen_seeds:
|
|
981
|
+
_seen_seeds.add(res.canonical_id)
|
|
982
|
+
ordered_seeds.append(res.canonical_id)
|
|
983
|
+
if res.qualified_name and _guard.allows_symbol(res.qualified_name) and res.qualified_name not in _seen_seeds:
|
|
984
|
+
_seen_seeds.add(res.qualified_name)
|
|
985
|
+
ordered_seeds.append(res.qualified_name)
|
|
986
|
+
if res.file_path:
|
|
987
|
+
try:
|
|
988
|
+
f_rows = con.execute(
|
|
989
|
+
"SELECT canonical_id FROM symbols WHERE path = ? LIMIT 5",
|
|
990
|
+
(res.file_path,),
|
|
991
|
+
).fetchall()
|
|
992
|
+
for fr in f_rows:
|
|
993
|
+
fs_cid = str(fr["canonical_id"])
|
|
994
|
+
if fs_cid and _guard.allows_canonical_id(fs_cid) and fs_cid not in _seen_seeds:
|
|
995
|
+
_seen_seeds.add(fs_cid)
|
|
996
|
+
ordered_seeds.append(fs_cid)
|
|
997
|
+
e_rows = con.execute(
|
|
998
|
+
"SELECT DISTINCT target FROM graph_edges WHERE file = ? AND relationship IN ('CALLS', 'IMPORTS') LIMIT 5",
|
|
999
|
+
(res.file_path,),
|
|
1000
|
+
).fetchall()
|
|
1001
|
+
for er in e_rows:
|
|
1002
|
+
e_tgt = str(er["target"])
|
|
1003
|
+
if e_tgt and _guard.allows_symbol(e_tgt) and e_tgt not in _seen_seeds:
|
|
1004
|
+
_seen_seeds.add(e_tgt)
|
|
1005
|
+
ordered_seeds.append(e_tgt)
|
|
1006
|
+
except Exception:
|
|
1007
|
+
pass
|
|
1008
|
+
|
|
1009
|
+
for ep in entry_points_out:
|
|
1010
|
+
h = str(ep.get("handler", ""))
|
|
1011
|
+
if h and _guard.allows_symbol(h) and h not in _seen_seeds:
|
|
1012
|
+
_seen_seeds.add(h)
|
|
1013
|
+
ordered_seeds.append(h)
|
|
1014
|
+
|
|
1015
|
+
for sym in list(task_spec.priority_targets or task_spec.targets):
|
|
1016
|
+
if sym and _guard.allows_symbol(sym) and sym not in _seen_seeds:
|
|
1017
|
+
_seen_seeds.add(sym)
|
|
1018
|
+
ordered_seeds.append(sym)
|
|
1019
|
+
|
|
1020
|
+
# Include top search results as seeds when grounded symbols are found
|
|
1021
|
+
for s_item in search_res[:3]:
|
|
1022
|
+
s_sym = str(s_item.get("symbol") or "")
|
|
1023
|
+
if s_sym and _guard.allows_symbol(s_sym) and s_sym not in _seen_seeds:
|
|
1024
|
+
_seen_seeds.add(s_sym)
|
|
1025
|
+
ordered_seeds.append(s_sym)
|
|
1026
|
+
|
|
1027
|
+
effective_max_depth = max_depth if max_depth is not None else _retrieval_policy.max_graph_depth
|
|
1028
|
+
effective_pkg_depth = max_package_depth if max_package_depth is not None else _retrieval_policy.max_package_depth
|
|
1029
|
+
|
|
1030
|
+
# Bounded seed policy
|
|
1031
|
+
graph_seed_policy = GraphSeedPolicy(
|
|
1032
|
+
source_type="EXPLICIT_TARGET",
|
|
1033
|
+
min_confidence="HIGH",
|
|
1034
|
+
max_seeds=max_graph_syms,
|
|
1035
|
+
max_depth=effective_max_depth,
|
|
1036
|
+
allowed_edges=_retrieval_policy.allowed_relationship_types,
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
# Monorepo / Package boundary detection early for package-aware limiting
|
|
1040
|
+
from codegraph.monorepo import detect_workspace
|
|
1041
|
+
ws_info = detect_workspace(repository, con=con)
|
|
1042
|
+
target_pkg = None
|
|
1043
|
+
for t in task_spec.targets:
|
|
1044
|
+
pkg = ws_info.get_package_for_file(t)
|
|
1045
|
+
if pkg and pkg.root_path != "":
|
|
1046
|
+
target_pkg = pkg
|
|
1047
|
+
break
|
|
1048
|
+
if not target_pkg:
|
|
1049
|
+
for t in task_spec.targets:
|
|
1050
|
+
pkg = ws_info.get_package_for_file(t)
|
|
1051
|
+
if pkg:
|
|
1052
|
+
target_pkg = pkg
|
|
1053
|
+
break
|
|
1054
|
+
if not target_pkg and ordered_seeds:
|
|
1055
|
+
for s in ordered_seeds:
|
|
1056
|
+
s_row = con.execute(
|
|
1057
|
+
"SELECT path FROM symbols WHERE canonical_id=? OR qualified_name=? OR name=? LIMIT 1",
|
|
1058
|
+
(s, s, s),
|
|
1059
|
+
).fetchone()
|
|
1060
|
+
if s_row:
|
|
1061
|
+
pkg = ws_info.get_package_for_file(str(s_row[0]))
|
|
1062
|
+
if pkg:
|
|
1063
|
+
target_pkg = pkg
|
|
1064
|
+
break
|
|
1065
|
+
|
|
1066
|
+
# Compute bounded package distance map (0=target, 1=direct dep/dependent, ..d=transitive, 99=unrelated)
|
|
1067
|
+
pkg_distances: dict[str, int] = {}
|
|
1068
|
+
if target_pkg:
|
|
1069
|
+
pkg_distances[target_pkg.package_id] = 0
|
|
1070
|
+
frontier = [target_pkg.package_id]
|
|
1071
|
+
for dist in range(1, max(1, effective_pkg_depth) + 1):
|
|
1072
|
+
next_frontier: list[str] = []
|
|
1073
|
+
for curr_pid in frontier:
|
|
1074
|
+
curr_pkg = ws_info.packages.get(curr_pid)
|
|
1075
|
+
if not curr_pkg:
|
|
1076
|
+
continue
|
|
1077
|
+
for dep_pid in curr_pkg.dependency_package_ids:
|
|
1078
|
+
if dep_pid in ws_info.packages and dep_pid not in pkg_distances:
|
|
1079
|
+
pkg_distances[dep_pid] = dist
|
|
1080
|
+
next_frontier.append(dep_pid)
|
|
1081
|
+
for dep_pkg in ws_info.get_dependent_packages(curr_pkg.package_id):
|
|
1082
|
+
if dep_pkg.package_id not in pkg_distances:
|
|
1083
|
+
pkg_distances[dep_pkg.package_id] = dist
|
|
1084
|
+
next_frontier.append(dep_pkg.package_id)
|
|
1085
|
+
frontier = next_frontier
|
|
1086
|
+
for pid in ws_info.packages:
|
|
1087
|
+
if pid not in pkg_distances:
|
|
1088
|
+
pkg_distances[pid] = 99
|
|
1089
|
+
|
|
1090
|
+
# Artifact-aware seed policy:
|
|
1091
|
+
# SOURCE: full participation
|
|
1092
|
+
# TEST: participation for test-aware tasks
|
|
1093
|
+
# GENERATED: deprioritized (placed after handwritten source)
|
|
1094
|
+
# BUILD_ARTIFACT, MINIFIED, BUNDLE, BINARY: suppressed from normal seeds
|
|
1095
|
+
# VENDOR: suppressed from normal seeds unless explicit opt-in
|
|
1096
|
+
filtered_seeds: list[str] = []
|
|
1097
|
+
deprioritized_seeds: list[str] = []
|
|
1098
|
+
excluded_artifacts_count = 0
|
|
1099
|
+
excluded_artifact_classes: set[str] = set()
|
|
1100
|
+
artifact_inclusions: list[dict[str, object]] = []
|
|
1101
|
+
|
|
1102
|
+
explicit_targets = set(task_spec.targets) | set(task_spec.priority_targets or ())
|
|
1103
|
+
|
|
1104
|
+
for s_candidate in ordered_seeds:
|
|
1105
|
+
if s_candidate in explicit_targets:
|
|
1106
|
+
filtered_seeds.append(s_candidate)
|
|
1107
|
+
continue
|
|
1108
|
+
cat_row = con.execute(
|
|
1109
|
+
"SELECT f.category FROM symbols s JOIN files f ON s.path = f.path "
|
|
1110
|
+
"WHERE s.canonical_id = ? OR s.qualified_name = ? OR s.name = ? LIMIT 1",
|
|
1111
|
+
(s_candidate, s_candidate, s_candidate),
|
|
1112
|
+
).fetchone()
|
|
1113
|
+
if cat_row:
|
|
1114
|
+
s_cat = str(cat_row[0])
|
|
1115
|
+
if s_cat in ("MINIFIED", "BUNDLE") and not include_bundles:
|
|
1116
|
+
excluded_artifacts_count += 1
|
|
1117
|
+
excluded_artifact_classes.add(s_cat)
|
|
1118
|
+
continue
|
|
1119
|
+
if s_cat in ("BUILD_ARTIFACT", "BINARY") and not include_build_artifacts:
|
|
1120
|
+
excluded_artifacts_count += 1
|
|
1121
|
+
excluded_artifact_classes.add(s_cat)
|
|
1122
|
+
continue
|
|
1123
|
+
if s_cat == "VENDOR" and not include_vendor:
|
|
1124
|
+
excluded_artifacts_count += 1
|
|
1125
|
+
excluded_artifact_classes.add(s_cat)
|
|
1126
|
+
continue
|
|
1127
|
+
if s_cat == "TEST" and task_spec.intent not in ("TEST", "DEBUG", "REVIEW"):
|
|
1128
|
+
continue
|
|
1129
|
+
if s_cat == "GENERATED" and not include_generated:
|
|
1130
|
+
deprioritized_seeds.append(s_candidate)
|
|
1131
|
+
continue
|
|
1132
|
+
filtered_seeds.append(s_candidate)
|
|
1133
|
+
|
|
1134
|
+
active_seeds = filtered_seeds + deprioritized_seeds
|
|
1135
|
+
findings_out: list[dict[str, object]] = []
|
|
1136
|
+
|
|
1137
|
+
for sym in active_seeds[:graph_seed_policy.max_seeds]:
|
|
1138
|
+
short = sym.split(".")[-1]
|
|
1139
|
+
if _retrieval_policy.include_callers and (
|
|
1140
|
+
retrieval_plan.entry_point_strategy in ("TRACE_FROM_ENDPOINT", "TRACE_HIERARCHY")
|
|
1141
|
+
or task_spec.intent in ("DEBUG", "TRACE", "IMPACT", "CHANGE", "REVIEW", "UNDERSTAND")
|
|
1142
|
+
):
|
|
1143
|
+
callers = find_callers(con, short, max_results=max_graph_results)
|
|
1144
|
+
for c in callers:
|
|
1145
|
+
caller_sym = c.get("symbol")
|
|
1146
|
+
rel = str(c.get("relationship", "CALLS"))
|
|
1147
|
+
if not _retrieval_policy.allows(rel):
|
|
1148
|
+
continue
|
|
1149
|
+
if not _guard.allows_symbol(str(caller_sym) if caller_sym else None):
|
|
1150
|
+
continue
|
|
1151
|
+
if not _guard.allows_file(str(c.get("file", ""))):
|
|
1152
|
+
continue
|
|
1153
|
+
candidate_dicts.append(
|
|
1154
|
+
{
|
|
1155
|
+
"file": c.get("file"),
|
|
1156
|
+
"symbol": caller_sym,
|
|
1157
|
+
"start_line": c.get("start_line", 1),
|
|
1158
|
+
"end_line": c.get("end_line", 1),
|
|
1159
|
+
"score": 0.45,
|
|
1160
|
+
"snippet": "",
|
|
1161
|
+
"relationship": rel,
|
|
1162
|
+
}
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
if _retrieval_policy.include_callees and task_spec.intent in ("EXPLAIN", "UNDERSTAND", "TRACE", "CHANGE", "REFACTOR", "DEBUG"):
|
|
1166
|
+
callees = find_callees(con, short, max_results=max_graph_results)
|
|
1167
|
+
for c in callees:
|
|
1168
|
+
callee_sym = c.get("callee")
|
|
1169
|
+
rel = str(c.get("relationship", "CALLS"))
|
|
1170
|
+
if not _retrieval_policy.allows(rel):
|
|
1171
|
+
continue
|
|
1172
|
+
if not _guard.allows_symbol(str(callee_sym) if callee_sym else None):
|
|
1173
|
+
continue
|
|
1174
|
+
if not _guard.allows_file(str(c.get("file", ""))):
|
|
1175
|
+
continue
|
|
1176
|
+
candidate_dicts.append(
|
|
1177
|
+
{
|
|
1178
|
+
"file": c.get("file", ""),
|
|
1179
|
+
"symbol": callee_sym,
|
|
1180
|
+
"start_line": c.get("line", 1),
|
|
1181
|
+
"end_line": c.get("line", 1),
|
|
1182
|
+
"score": 0.50,
|
|
1183
|
+
"snippet": "",
|
|
1184
|
+
"relationship": rel,
|
|
1185
|
+
}
|
|
1186
|
+
)
|
|
1187
|
+
# Depth 2 callee and parallel discovery
|
|
1188
|
+
if effective_max_depth >= 2 and callee_sym:
|
|
1189
|
+
c2_list = find_callees(con, str(callee_sym), max_results=max_graph_results)
|
|
1190
|
+
for c2 in c2_list:
|
|
1191
|
+
c2_sym = c2.get("callee")
|
|
1192
|
+
c2_rel = str(c2.get("relationship", "CALLS"))
|
|
1193
|
+
if not _retrieval_policy.allows(c2_rel):
|
|
1194
|
+
continue
|
|
1195
|
+
if not _guard.allows_symbol(str(c2_sym) if c2_sym else None):
|
|
1196
|
+
continue
|
|
1197
|
+
if not _guard.allows_file(str(c2.get("file", ""))):
|
|
1198
|
+
continue
|
|
1199
|
+
candidate_dicts.append(
|
|
1200
|
+
{
|
|
1201
|
+
"file": c2.get("file", ""),
|
|
1202
|
+
"symbol": c2_sym,
|
|
1203
|
+
"start_line": c2.get("line", 1),
|
|
1204
|
+
"end_line": c2.get("line", 1),
|
|
1205
|
+
"score": 0.45,
|
|
1206
|
+
"snippet": "",
|
|
1207
|
+
"relationship": c2_rel,
|
|
1208
|
+
}
|
|
1209
|
+
)
|
|
1210
|
+
if _retrieval_policy.allows("PARALLEL_IMPLEMENTATION"):
|
|
1211
|
+
p2_list = find_parallel_implementations(con, str(callee_sym), max_results=2)
|
|
1212
|
+
for p2 in p2_list:
|
|
1213
|
+
p2_sym = p2.get("symbol")
|
|
1214
|
+
if not _guard.allows_symbol(str(p2_sym) if p2_sym else None):
|
|
1215
|
+
continue
|
|
1216
|
+
if not _guard.allows_file(str(p2.get("file", ""))):
|
|
1217
|
+
continue
|
|
1218
|
+
candidate_dicts.append(
|
|
1219
|
+
{
|
|
1220
|
+
"file": p2.get("file", ""),
|
|
1221
|
+
"symbol": p2_sym,
|
|
1222
|
+
"canonical_id": p2.get("canonical_id"),
|
|
1223
|
+
"start_line": p2.get("start_line", 1),
|
|
1224
|
+
"end_line": p2.get("end_line", 1),
|
|
1225
|
+
"score": 0.55,
|
|
1226
|
+
"snippet": "",
|
|
1227
|
+
"relationship": "PARALLEL_IMPLEMENTATION",
|
|
1228
|
+
}
|
|
1229
|
+
)
|
|
1230
|
+
|
|
1231
|
+
if _retrieval_policy.allows("PARALLEL_IMPLEMENTATION"):
|
|
1232
|
+
parallels = find_parallel_implementations(con, short, max_results=3)
|
|
1233
|
+
for p in parallels:
|
|
1234
|
+
p_sym = p.get("symbol")
|
|
1235
|
+
if not _guard.allows_symbol(str(p_sym) if p_sym else None):
|
|
1236
|
+
continue
|
|
1237
|
+
if not _guard.allows_file(str(p.get("file", ""))):
|
|
1238
|
+
continue
|
|
1239
|
+
candidate_dicts.append(
|
|
1240
|
+
{
|
|
1241
|
+
"file": p.get("file", ""),
|
|
1242
|
+
"symbol": p_sym,
|
|
1243
|
+
"canonical_id": p.get("canonical_id"),
|
|
1244
|
+
"start_line": p.get("start_line", 1),
|
|
1245
|
+
"end_line": p.get("end_line", 1),
|
|
1246
|
+
"score": 0.65,
|
|
1247
|
+
"snippet": "",
|
|
1248
|
+
"relationship": "PARALLEL_IMPLEMENTATION",
|
|
1249
|
+
}
|
|
1250
|
+
)
|
|
1251
|
+
|
|
1252
|
+
# Phase 10: Semantic edge candidate discovery (DI providers, registrations, events, routes, tests)
|
|
1253
|
+
# Ensures coverage-aware optimization discovers high-value semantic neighbors even when lexically distinct
|
|
1254
|
+
try:
|
|
1255
|
+
sem_rows = con.execute(
|
|
1256
|
+
"""
|
|
1257
|
+
SELECT source, target, relationship, confidence, evidence_class, file, start_line, evidence
|
|
1258
|
+
FROM graph_edges
|
|
1259
|
+
WHERE source = ? OR target = ? OR source LIKE ? OR target LIKE ?
|
|
1260
|
+
LIMIT 30
|
|
1261
|
+
""",
|
|
1262
|
+
(sym, sym, f"%::{short}", f"%::{short}"),
|
|
1263
|
+
).fetchall()
|
|
1264
|
+
for srow in sem_rows:
|
|
1265
|
+
s_rel = str(srow["relationship"] or "")
|
|
1266
|
+
s_conf = str(srow["confidence"] or "HIGH").upper()
|
|
1267
|
+
s_ev_cls = str(srow["evidence_class"] or "AST_VERIFIED").upper()
|
|
1268
|
+
if s_ev_cls in ("UNKNOWN", "POSSIBLE") or s_conf == "LOW":
|
|
1269
|
+
findings_out.append({
|
|
1270
|
+
"type": "EPISTEMIC_EDGE",
|
|
1271
|
+
"source": srow["source"],
|
|
1272
|
+
"target": srow["target"],
|
|
1273
|
+
"relationship": s_rel,
|
|
1274
|
+
"evidence_class": s_ev_cls,
|
|
1275
|
+
"confidence": s_conf,
|
|
1276
|
+
"file": srow["file"],
|
|
1277
|
+
"line": srow["start_line"],
|
|
1278
|
+
"evidence": srow["evidence"] or "",
|
|
1279
|
+
"status": "UNKNOWN" if s_ev_cls == "UNKNOWN" else "INFERENCE",
|
|
1280
|
+
})
|
|
1281
|
+
if not _retrieval_policy.allows(s_rel):
|
|
1282
|
+
continue
|
|
1283
|
+
other_id = str(srow["target"]) if str(srow["source"]).endswith(short) or str(srow["source"]) == sym else str(srow["source"])
|
|
1284
|
+
target_symbols.add(other_id)
|
|
1285
|
+
sym_lookup = con.execute(
|
|
1286
|
+
"SELECT canonical_id, qualified_name, name, path, start_line, end_line FROM symbols "
|
|
1287
|
+
"WHERE canonical_id = ? OR qualified_name = ? OR name = ? LIMIT 1",
|
|
1288
|
+
(other_id, other_id, other_id.split("::")[-1].split(".")[-1]),
|
|
1289
|
+
).fetchone()
|
|
1290
|
+
if sym_lookup:
|
|
1291
|
+
o_file = str(sym_lookup["path"])
|
|
1292
|
+
o_sym = str(sym_lookup["qualified_name"] or sym_lookup["name"])
|
|
1293
|
+
if _guard.allows_symbol(o_sym) and _guard.allows_file(o_file):
|
|
1294
|
+
candidate_dicts.append({
|
|
1295
|
+
"file": o_file,
|
|
1296
|
+
"symbol": o_sym,
|
|
1297
|
+
"canonical_id": str(sym_lookup["canonical_id"]),
|
|
1298
|
+
"start_line": int(sym_lookup["start_line"] or 1),
|
|
1299
|
+
"end_line": int(sym_lookup["end_line"] or 1),
|
|
1300
|
+
"score": 0.65,
|
|
1301
|
+
"snippet": "",
|
|
1302
|
+
"relationship": s_rel,
|
|
1303
|
+
"confidence": s_conf,
|
|
1304
|
+
})
|
|
1305
|
+
except Exception:
|
|
1306
|
+
pass
|
|
1307
|
+
|
|
1308
|
+
# Also discover parallel implementations for top search results
|
|
1309
|
+
if _retrieval_policy.allows("PARALLEL_IMPLEMENTATION"):
|
|
1310
|
+
for s_item in search_res[:4]:
|
|
1311
|
+
s_sym = str(s_item.get("symbol") or "")
|
|
1312
|
+
if s_sym and s_sym not in _seen_seeds:
|
|
1313
|
+
parallels = find_parallel_implementations(con, s_sym, max_results=2)
|
|
1314
|
+
for p in parallels:
|
|
1315
|
+
p_sym = p.get("symbol")
|
|
1316
|
+
if not _guard.allows_symbol(str(p_sym) if p_sym else None):
|
|
1317
|
+
continue
|
|
1318
|
+
if not _guard.allows_file(str(p.get("file", ""))):
|
|
1319
|
+
continue
|
|
1320
|
+
candidate_dicts.append(
|
|
1321
|
+
{
|
|
1322
|
+
"file": p.get("file", ""),
|
|
1323
|
+
"symbol": p_sym,
|
|
1324
|
+
"canonical_id": p.get("canonical_id"),
|
|
1325
|
+
"start_line": p.get("start_line", 1),
|
|
1326
|
+
"end_line": p.get("end_line", 1),
|
|
1327
|
+
"score": 0.60,
|
|
1328
|
+
"snippet": "",
|
|
1329
|
+
"relationship": "PARALLEL_IMPLEMENTATION",
|
|
1330
|
+
}
|
|
1331
|
+
)
|
|
1332
|
+
|
|
1333
|
+
timing.graph_ms = (time.perf_counter() - t_graph_start) * 1000
|
|
1334
|
+
|
|
1335
|
+
# Related tests
|
|
1336
|
+
t_tests_start = time.perf_counter()
|
|
1337
|
+
tests_out: list[TestRef] = []
|
|
1338
|
+
seen_test_keys: set[tuple[str, str | None, str]] = set()
|
|
1339
|
+
if retrieval_plan.include_tests or _retrieval_policy.include_tests:
|
|
1340
|
+
test_sym_targets = list(task_spec.priority_targets or task_spec.targets)
|
|
1341
|
+
if not test_sym_targets:
|
|
1342
|
+
test_sym_targets = list(target_symbols)
|
|
1343
|
+
for sym in test_sym_targets[:max_test_syms]:
|
|
1344
|
+
test_results = find_related_tests(con, sym, max_results=max_test_results)
|
|
1345
|
+
for test_item in test_results:
|
|
1346
|
+
if "result" in test_item:
|
|
1347
|
+
continue
|
|
1348
|
+
t_file = str(test_item.get("file", ""))
|
|
1349
|
+
t_sym = str(test_item["symbol"]) if test_item.get("symbol") is not None else None
|
|
1350
|
+
t_rel = str(test_item.get("relationship", "TESTS"))
|
|
1351
|
+
t_key = (t_file, t_sym, t_rel)
|
|
1352
|
+
if t_key in seen_test_keys:
|
|
1353
|
+
continue
|
|
1354
|
+
seen_test_keys.add(t_key)
|
|
1355
|
+
candidate_dicts.append(
|
|
1356
|
+
{
|
|
1357
|
+
"file": t_file,
|
|
1358
|
+
"symbol": t_sym,
|
|
1359
|
+
"start_line": test_item.get("start_line", 1),
|
|
1360
|
+
"end_line": test_item.get("start_line", 1),
|
|
1361
|
+
"score": 0.60,
|
|
1362
|
+
"snippet": "",
|
|
1363
|
+
"relationship": t_rel,
|
|
1364
|
+
}
|
|
1365
|
+
)
|
|
1366
|
+
tests_out.append(
|
|
1367
|
+
TestRef(
|
|
1368
|
+
file=t_file,
|
|
1369
|
+
symbol=t_sym,
|
|
1370
|
+
relationship=t_rel,
|
|
1371
|
+
confidence=str(test_item.get("confidence", "LOW")),
|
|
1372
|
+
evidence=str(test_item.get("evidence", "")),
|
|
1373
|
+
)
|
|
1374
|
+
)
|
|
1375
|
+
timing.tests_ms = (time.perf_counter() - t_tests_start) * 1000
|
|
1376
|
+
|
|
1377
|
+
# Pre-ranking artifact & package annotation on candidate_dicts
|
|
1378
|
+
from codegraph.indexing.classifier import classify_file
|
|
1379
|
+
from codegraph.security import is_sensitive_path
|
|
1380
|
+
filtered_candidate_dicts: list[dict[str, object]] = []
|
|
1381
|
+
for cd in candidate_dicts:
|
|
1382
|
+
c_file = str(cd.get("file") or "")
|
|
1383
|
+
if c_file and is_sensitive_path(c_file):
|
|
1384
|
+
continue
|
|
1385
|
+
c_sym = str(cd.get("symbol") or "")
|
|
1386
|
+
c_cat = str(cd.get("category") or "")
|
|
1387
|
+
if not c_cat and c_file:
|
|
1388
|
+
frow = con.execute("SELECT category FROM files WHERE path=? LIMIT 1", (c_file,)).fetchone()
|
|
1389
|
+
c_cat = str(frow[0]) if frow and frow[0] else str(classify_file(c_file).value)
|
|
1390
|
+
cd["category"] = c_cat
|
|
1391
|
+
|
|
1392
|
+
is_explicit = c_file in explicit_targets or c_sym in explicit_targets
|
|
1393
|
+
if not is_explicit:
|
|
1394
|
+
if c_cat in ("MINIFIED", "BUNDLE") and not include_bundles:
|
|
1395
|
+
excluded_artifacts_count += 1
|
|
1396
|
+
excluded_artifact_classes.add(c_cat)
|
|
1397
|
+
continue
|
|
1398
|
+
if c_cat in ("BUILD_ARTIFACT", "BINARY") and not include_build_artifacts:
|
|
1399
|
+
excluded_artifacts_count += 1
|
|
1400
|
+
excluded_artifact_classes.add(c_cat)
|
|
1401
|
+
continue
|
|
1402
|
+
if c_cat == "VENDOR" and not include_vendor:
|
|
1403
|
+
excluded_artifacts_count += 1
|
|
1404
|
+
excluded_artifact_classes.add(c_cat)
|
|
1405
|
+
continue
|
|
1406
|
+
else:
|
|
1407
|
+
if c_cat in ("MINIFIED", "BUNDLE", "BUILD_ARTIFACT", "VENDOR", "GENERATED"):
|
|
1408
|
+
artifact_inclusions.append({
|
|
1409
|
+
"file": c_file,
|
|
1410
|
+
"artifact_class": c_cat,
|
|
1411
|
+
"reason": "Explicitly targeted by task",
|
|
1412
|
+
})
|
|
1413
|
+
|
|
1414
|
+
if c_cat in ("MINIFIED", "BUNDLE") and include_bundles:
|
|
1415
|
+
artifact_inclusions.append({"file": c_file, "artifact_class": c_cat, "reason": "Explicit opt-in: include_bundles=True"})
|
|
1416
|
+
elif c_cat in ("BUILD_ARTIFACT", "BINARY") and include_build_artifacts:
|
|
1417
|
+
artifact_inclusions.append({"file": c_file, "artifact_class": c_cat, "reason": "Explicit opt-in: include_build_artifacts=True"})
|
|
1418
|
+
elif c_cat == "VENDOR" and include_vendor:
|
|
1419
|
+
artifact_inclusions.append({"file": c_file, "artifact_class": c_cat, "reason": "Explicit opt-in: include_vendor=True"})
|
|
1420
|
+
elif c_cat == "GENERATED" and include_generated:
|
|
1421
|
+
artifact_inclusions.append({"file": c_file, "artifact_class": c_cat, "reason": "Explicit opt-in: include_generated=True"})
|
|
1422
|
+
|
|
1423
|
+
if target_pkg and c_file:
|
|
1424
|
+
c_pkg = ws_info.get_package_for_file(c_file)
|
|
1425
|
+
if c_pkg:
|
|
1426
|
+
cd["package_id"] = c_pkg.package_id
|
|
1427
|
+
cd["package_distance"] = pkg_distances.get(c_pkg.package_id, 99)
|
|
1428
|
+
|
|
1429
|
+
filtered_candidate_dicts.append(cd)
|
|
1430
|
+
|
|
1431
|
+
candidate_dicts = filtered_candidate_dicts
|
|
1432
|
+
|
|
1433
|
+
# 6. Rank candidates
|
|
1434
|
+
t_rank_start = time.perf_counter()
|
|
1435
|
+
legacy_intent_str = str(caller_raw_intent or task_spec.intent.lower())
|
|
1436
|
+
ranked: list[RankedItem] = rank(
|
|
1437
|
+
candidate_dicts,
|
|
1438
|
+
query=task_display_str,
|
|
1439
|
+
intent=legacy_intent_str,
|
|
1440
|
+
target_symbols=target_symbols,
|
|
1441
|
+
recent_paths=recent_paths,
|
|
1442
|
+
max_results=effective_top_k * 4,
|
|
1443
|
+
allowed_relationships=_retrieval_policy.allowed_relationship_types,
|
|
1444
|
+
)
|
|
1445
|
+
timing.ranking_ms = (time.perf_counter() - t_rank_start) * 1000
|
|
1446
|
+
|
|
1447
|
+
# 7. Bounded Snippets and Evidence
|
|
1448
|
+
from codegraph.security import redact_secrets
|
|
1449
|
+
t_evidence_start = time.perf_counter()
|
|
1450
|
+
per_snippet_max_lines = max(1, min(35, min(max_lines, HARD_MAX_LINES)))
|
|
1451
|
+
for item in ranked:
|
|
1452
|
+
if not item.snippet and item.symbol:
|
|
1453
|
+
row = con.execute(
|
|
1454
|
+
"SELECT content FROM chunks WHERE symbol=? LIMIT 1", (item.symbol,)
|
|
1455
|
+
).fetchone()
|
|
1456
|
+
if row:
|
|
1457
|
+
snip, _, _ = select_bounded_snippet(
|
|
1458
|
+
redact_secrets(str(row["content"])),
|
|
1459
|
+
start_line=item.start_line,
|
|
1460
|
+
end_line=item.end_line,
|
|
1461
|
+
max_lines=per_snippet_max_lines,
|
|
1462
|
+
max_chars=800,
|
|
1463
|
+
)
|
|
1464
|
+
snip = redact_secrets(snip)
|
|
1465
|
+
object.__setattr__(item, "snippet", snip)
|
|
1466
|
+
object.__setattr__(item, "token_estimate", _token_estimate(snip))
|
|
1467
|
+
elif item.snippet:
|
|
1468
|
+
snip, _, _ = select_bounded_snippet(
|
|
1469
|
+
redact_secrets(item.snippet),
|
|
1470
|
+
start_line=item.start_line,
|
|
1471
|
+
end_line=item.end_line,
|
|
1472
|
+
max_lines=per_snippet_max_lines,
|
|
1473
|
+
max_chars=800,
|
|
1474
|
+
)
|
|
1475
|
+
snip = redact_secrets(snip)
|
|
1476
|
+
object.__setattr__(item, "snippet", snip)
|
|
1477
|
+
object.__setattr__(item, "token_estimate", _token_estimate(snip))
|
|
1478
|
+
timing.evidence_ms = (time.perf_counter() - t_evidence_start) * 1000
|
|
1479
|
+
|
|
1480
|
+
# 8. Token Budget & Coverage-Aware Optimization
|
|
1481
|
+
t_compile_start = time.perf_counter()
|
|
1482
|
+
candidate_items: list[CandidateContextItem] = []
|
|
1483
|
+
for idx, item in enumerate(ranked):
|
|
1484
|
+
layer = "GENERAL"
|
|
1485
|
+
if any(ep["file"] == item.file for ep in entry_points_out):
|
|
1486
|
+
layer = "ENTRYPOINT"
|
|
1487
|
+
elif "test" in item.file.lower():
|
|
1488
|
+
layer = "TEST"
|
|
1489
|
+
elif item.file in recent_paths:
|
|
1490
|
+
layer = "GIT"
|
|
1491
|
+
elif "service" in item.file.lower() or "controller" in item.file.lower():
|
|
1492
|
+
layer = "SERVICE"
|
|
1493
|
+
elif "model" in item.file.lower() or "db" in item.file.lower() or "schema" in item.file.lower():
|
|
1494
|
+
layer = "DATA"
|
|
1495
|
+
|
|
1496
|
+
canon = item.canonical_id
|
|
1497
|
+
if not canon and item.symbol:
|
|
1498
|
+
srow = con.execute(
|
|
1499
|
+
"SELECT canonical_id FROM symbols WHERE (canonical_id=? OR qualified_name=? OR name=?) AND path=? LIMIT 1",
|
|
1500
|
+
(item.symbol, item.symbol, item.symbol, item.file),
|
|
1501
|
+
).fetchone()
|
|
1502
|
+
if not srow and not item.file:
|
|
1503
|
+
srow = con.execute(
|
|
1504
|
+
"SELECT canonical_id FROM symbols WHERE canonical_id=? OR qualified_name=? LIMIT 1",
|
|
1505
|
+
(item.symbol, item.symbol),
|
|
1506
|
+
).fetchone()
|
|
1507
|
+
elif not srow and item.symbol:
|
|
1508
|
+
srow = con.execute(
|
|
1509
|
+
"SELECT canonical_id FROM symbols WHERE canonical_id=? LIMIT 1",
|
|
1510
|
+
(item.symbol,),
|
|
1511
|
+
).fetchone()
|
|
1512
|
+
if srow:
|
|
1513
|
+
canon = srow["canonical_id"]
|
|
1514
|
+
|
|
1515
|
+
item_pkg = ws_info.get_package_for_file(item.file) if item.file else None
|
|
1516
|
+
item_pkg_id = item_pkg.package_id if item_pkg else None
|
|
1517
|
+
item_pkg_dist = pkg_distances.get(item_pkg_id, 0) if (target_pkg and item_pkg_id) else 0
|
|
1518
|
+
|
|
1519
|
+
frow = con.execute("SELECT category FROM files WHERE path=? LIMIT 1", (item.file,)).fetchone() if item.file else None
|
|
1520
|
+
item_art_cls = str(frow[0]) if frow and frow[0] else str(classify_file(item.file).value)
|
|
1521
|
+
|
|
1522
|
+
# Determine relationship tag from reasons if present
|
|
1523
|
+
rel_tag = ""
|
|
1524
|
+
for rs in item.reasons:
|
|
1525
|
+
if rs.code == "DI_PROVIDER":
|
|
1526
|
+
rel_tag = "PROVIDES"
|
|
1527
|
+
elif rs.code == "SEMANTIC_DISPATCH":
|
|
1528
|
+
rel_tag = "REGISTERS"
|
|
1529
|
+
elif rs.code == "ENDPOINT_HANDLER":
|
|
1530
|
+
rel_tag = "HANDLED_BY"
|
|
1531
|
+
elif rs.code in ("DIRECT_CALLER", "POSSIBLE_CALLER"):
|
|
1532
|
+
rel_tag = "CALLED_BY"
|
|
1533
|
+
elif rs.code == "DIRECT_CALLEE":
|
|
1534
|
+
rel_tag = "CALLS"
|
|
1535
|
+
elif rs.code == "TEST_RELATION":
|
|
1536
|
+
rel_tag = "TESTS"
|
|
1537
|
+
elif rs.code == "PACKAGE_DEPENDENCY":
|
|
1538
|
+
rel_tag = "DEPENDS_ON_PACKAGE"
|
|
1539
|
+
|
|
1540
|
+
candidate_items.append(
|
|
1541
|
+
CandidateContextItem(
|
|
1542
|
+
item_id=f"c_{idx}_{item.file}_{item.start_line}",
|
|
1543
|
+
file_path=item.file,
|
|
1544
|
+
start_line=item.start_line,
|
|
1545
|
+
end_line=item.end_line,
|
|
1546
|
+
canonical_id=canon,
|
|
1547
|
+
estimated_tokens=item.token_estimate,
|
|
1548
|
+
relevance_score=item.score,
|
|
1549
|
+
evidence_quality=0.9 if item.file not in stale_paths else 0.4,
|
|
1550
|
+
freshness="STALE" if item.file in stale_paths else "FRESH",
|
|
1551
|
+
coverage_layer=layer,
|
|
1552
|
+
source_type="symbol" if item.symbol else "chunk",
|
|
1553
|
+
snippet=item.snippet,
|
|
1554
|
+
confidence="HIGH" if item.file not in stale_paths else "LOW",
|
|
1555
|
+
relationship_value=0.8 if layer in ("ENTRYPOINT", "SERVICE", "TEST") or rel_tag else 0.4,
|
|
1556
|
+
epistemic_status="FACT" if item.file not in stale_paths else "UNKNOWN",
|
|
1557
|
+
data={"reasons": item.reasons, "symbol": item.symbol, "relationship": rel_tag},
|
|
1558
|
+
package_id=item_pkg_id,
|
|
1559
|
+
package_distance=item_pkg_dist,
|
|
1560
|
+
artifact_class=item_art_cls,
|
|
1561
|
+
)
|
|
1562
|
+
)
|
|
1563
|
+
|
|
1564
|
+
budget_spec = ContextBudgetSpec(
|
|
1565
|
+
max_tokens=max_tokens,
|
|
1566
|
+
max_symbols=max_symbols,
|
|
1567
|
+
max_relationships=max_relationships,
|
|
1568
|
+
max_files=max_files,
|
|
1569
|
+
max_lines=max_lines,
|
|
1570
|
+
max_depth=effective_max_depth,
|
|
1571
|
+
)
|
|
1572
|
+
|
|
1573
|
+
selected_items, budget = optimize_context_budget(
|
|
1574
|
+
candidates=candidate_items,
|
|
1575
|
+
token_budget=max_tokens,
|
|
1576
|
+
task_spec=task_spec,
|
|
1577
|
+
budget_spec=budget_spec,
|
|
1578
|
+
required_dimensions=_retrieval_policy.required_dimensions,
|
|
1579
|
+
verified_claims_count=len(route_relationships) + len(tests_out),
|
|
1580
|
+
tool_calls=1,
|
|
1581
|
+
)
|
|
1582
|
+
timing.compilation_ms = (time.perf_counter() - t_compile_start) * 1000
|
|
1583
|
+
|
|
1584
|
+
# 9. Assembly, Deduplication & Evidence Compression
|
|
1585
|
+
t_serial_start = time.perf_counter()
|
|
1586
|
+
selected_paths = {item.file_path for item in selected_items if item.file_path}
|
|
1587
|
+
discarded_paths = {item.file_path for item in candidate_items if item.file_path and item.file_path not in selected_paths}
|
|
1588
|
+
|
|
1589
|
+
symbols_out: list[SymbolRef] = []
|
|
1590
|
+
seen_canonical_syms: set[str] = set()
|
|
1591
|
+
for it in selected_items:
|
|
1592
|
+
sym_name = it.data.get("symbol")
|
|
1593
|
+
if sym_name:
|
|
1594
|
+
known_cid = it.canonical_id or it.data.get("canonical_id")
|
|
1595
|
+
if known_cid:
|
|
1596
|
+
row = con.execute(
|
|
1597
|
+
"SELECT canonical_id, kind FROM symbols WHERE canonical_id=? LIMIT 1",
|
|
1598
|
+
(known_cid,),
|
|
1599
|
+
).fetchone()
|
|
1600
|
+
else:
|
|
1601
|
+
row = con.execute(
|
|
1602
|
+
"SELECT canonical_id, kind FROM symbols WHERE (canonical_id=? OR qualified_name=? OR name=?) AND path=? LIMIT 1",
|
|
1603
|
+
(sym_name, sym_name, sym_name, it.file_path),
|
|
1604
|
+
).fetchone()
|
|
1605
|
+
if not row and not it.file_path:
|
|
1606
|
+
row = con.execute(
|
|
1607
|
+
"SELECT canonical_id, kind FROM symbols WHERE canonical_id=? OR qualified_name=? LIMIT 1",
|
|
1608
|
+
(sym_name, sym_name),
|
|
1609
|
+
).fetchone()
|
|
1610
|
+
cid = known_cid or (row["canonical_id"] if row and "canonical_id" in row.keys() else None)
|
|
1611
|
+
dedup_id = str(cid) if cid else f"{it.file_path}::{sym_name}"
|
|
1612
|
+
if dedup_id in seen_canonical_syms:
|
|
1613
|
+
continue
|
|
1614
|
+
seen_canonical_syms.add(dedup_id)
|
|
1615
|
+
kind = row["kind"] if row else "unknown"
|
|
1616
|
+
reasons = it.data.get("reasons", [])
|
|
1617
|
+
symbols_out.append(
|
|
1618
|
+
SymbolRef(
|
|
1619
|
+
symbol=sym_name,
|
|
1620
|
+
file=it.file_path,
|
|
1621
|
+
kind=kind,
|
|
1622
|
+
start_line=it.start_line,
|
|
1623
|
+
end_line=it.end_line,
|
|
1624
|
+
score=it.relevance_score,
|
|
1625
|
+
reasons=[
|
|
1626
|
+
RankingReasonModel(code=rs.code, label=rs.label, contribution=rs.contribution)
|
|
1627
|
+
for rs in reasons
|
|
1628
|
+
],
|
|
1629
|
+
canonical_id=cid or it.canonical_id,
|
|
1630
|
+
reason_code=it.reason_code,
|
|
1631
|
+
package_id=it.package_id,
|
|
1632
|
+
)
|
|
1633
|
+
)
|
|
1634
|
+
|
|
1635
|
+
# ── Relationships extraction & compression ──────────────────────────
|
|
1636
|
+
relationships_raw: list[Relationship] = list(route_relationships)
|
|
1637
|
+
|
|
1638
|
+
try:
|
|
1639
|
+
all_syms = (
|
|
1640
|
+
list(target_symbols)
|
|
1641
|
+
+ [s.split(".")[-1] for s in target_symbols]
|
|
1642
|
+
+ [s.canonical_id for s in symbols_out if s.canonical_id]
|
|
1643
|
+
+ [s.symbol for s in symbols_out if s.symbol]
|
|
1644
|
+
)
|
|
1645
|
+
if target_pkg:
|
|
1646
|
+
all_syms.append(target_pkg.package_id)
|
|
1647
|
+
all_syms.append(target_pkg.name)
|
|
1648
|
+
all_syms = list(dict.fromkeys(all_syms))
|
|
1649
|
+
if all_syms:
|
|
1650
|
+
placeholders = ",".join("?" for _ in all_syms)
|
|
1651
|
+
edge_rows = con.execute(
|
|
1652
|
+
f"""
|
|
1653
|
+
SELECT source, target, relationship, confidence, evidence_class, file, start_line, evidence
|
|
1654
|
+
FROM graph_edges
|
|
1655
|
+
WHERE source IN ({placeholders}) OR target IN ({placeholders})
|
|
1656
|
+
LIMIT 100
|
|
1657
|
+
""",
|
|
1658
|
+
all_syms + all_syms,
|
|
1659
|
+
).fetchall()
|
|
1660
|
+
for er in edge_rows:
|
|
1661
|
+
ev_cls = str(er["evidence_class"] if "evidence_class" in er.keys() and er["evidence_class"] else "AST_VERIFIED")
|
|
1662
|
+
conf_val = str(er["confidence"] or "HIGH").upper()
|
|
1663
|
+
status_val = "FACT" if conf_val == "HIGH" and ev_cls not in ("POSSIBLE", "UNKNOWN") else (
|
|
1664
|
+
"UNKNOWN" if ev_cls == "UNKNOWN" else "INFERENCE"
|
|
1665
|
+
)
|
|
1666
|
+
relationships_raw.append(
|
|
1667
|
+
Relationship(
|
|
1668
|
+
source=er["source"],
|
|
1669
|
+
target=er["target"],
|
|
1670
|
+
relationship=er["relationship"],
|
|
1671
|
+
confidence=conf_val,
|
|
1672
|
+
file=er["file"],
|
|
1673
|
+
start_line=er["start_line"],
|
|
1674
|
+
evidence=er["evidence"] or "",
|
|
1675
|
+
status=status_val,
|
|
1676
|
+
evidence_class=ev_cls,
|
|
1677
|
+
)
|
|
1678
|
+
)
|
|
1679
|
+
except Exception:
|
|
1680
|
+
pass
|
|
1681
|
+
|
|
1682
|
+
# Backfill callers
|
|
1683
|
+
for sym in list(target_symbols)[:5]:
|
|
1684
|
+
short = sym.split(".")[-1]
|
|
1685
|
+
callers = find_callers(con, short, max_results=10)
|
|
1686
|
+
for c in callers:
|
|
1687
|
+
c_file = str(c.get("file", ""))
|
|
1688
|
+
if c_file in selected_paths:
|
|
1689
|
+
raw_c_conf = str(c.get("confidence", "LOW")).upper()
|
|
1690
|
+
c_conf = "MEDIUM" if raw_c_conf == "HIGH" else raw_c_conf
|
|
1691
|
+
relationships_raw.append(
|
|
1692
|
+
Relationship(
|
|
1693
|
+
source=c_file,
|
|
1694
|
+
target=sym,
|
|
1695
|
+
relationship="POSSIBLE_CALLS",
|
|
1696
|
+
confidence=c_conf,
|
|
1697
|
+
file=c_file,
|
|
1698
|
+
evidence=str(c.get("evidence", "")),
|
|
1699
|
+
status="INFERENCE",
|
|
1700
|
+
evidence_class="POSSIBLE",
|
|
1701
|
+
)
|
|
1702
|
+
)
|
|
1703
|
+
|
|
1704
|
+
# ── v2.1 & Phase 10 HARD SAFETY & INTEGRITY INVARIANT ───────────────
|
|
1705
|
+
# 1. Symbols
|
|
1706
|
+
symbols_out = [
|
|
1707
|
+
s for s in symbols_out
|
|
1708
|
+
if _guard.allows_symbol(s.symbol) and _guard.allows_canonical_id(s.canonical_id) and _guard.allows_file(s.file)
|
|
1709
|
+
][: budget.max_symbols]
|
|
1710
|
+
|
|
1711
|
+
# 2. Relationships (filtered + deterministically compressed)
|
|
1712
|
+
filtered_rels: list[Relationship] = [
|
|
1713
|
+
r for r in relationships_raw
|
|
1714
|
+
if _guard.allows_relationship(r.source, r.relationship, r.target)
|
|
1715
|
+
and (_guard.allows_file(r.file) if r.file else True)
|
|
1716
|
+
and _retrieval_policy.allows(r.relationship)
|
|
1717
|
+
]
|
|
1718
|
+
relationships_out, compression_stats = compress_relationships(
|
|
1719
|
+
filtered_rels, max_relationships=budget.max_relationships
|
|
1720
|
+
)
|
|
1721
|
+
|
|
1722
|
+
# 3. Tests (deduplicated)
|
|
1723
|
+
tests_out = [
|
|
1724
|
+
t for t in tests_out
|
|
1725
|
+
if _guard.allows_test(t.file, t.symbol)
|
|
1726
|
+
]
|
|
1727
|
+
|
|
1728
|
+
# 4. Entry points & framework facts (deduplicated by endpoint_id)
|
|
1729
|
+
seen_ep_ids: set[str] = set()
|
|
1730
|
+
dedup_entry_points: list[dict[str, object]] = []
|
|
1731
|
+
for ep in entry_points_out:
|
|
1732
|
+
if not _guard.allows_endpoint(
|
|
1733
|
+
str(ep.get("route_path")),
|
|
1734
|
+
str(ep.get("handler")),
|
|
1735
|
+
str(ep.get("file")),
|
|
1736
|
+
):
|
|
1737
|
+
continue
|
|
1738
|
+
eid = str(ep.get("endpoint_id") or "")
|
|
1739
|
+
if eid and eid in seen_ep_ids:
|
|
1740
|
+
continue
|
|
1741
|
+
if eid:
|
|
1742
|
+
seen_ep_ids.add(eid)
|
|
1743
|
+
dedup_entry_points.append(ep)
|
|
1744
|
+
entry_points_out = dedup_entry_points
|
|
1745
|
+
|
|
1746
|
+
framework_facts_out = [
|
|
1747
|
+
ff for ff in framework_facts_out
|
|
1748
|
+
if _guard.allows_symbol(str(ff.get("handler"))) and _guard.allows_file(str(ff.get("file")))
|
|
1749
|
+
]
|
|
1750
|
+
|
|
1751
|
+
# 5. Evidence (bounded & deduplicated by canonical location)
|
|
1752
|
+
evidence_out: list[EvidenceRef] = []
|
|
1753
|
+
seen_ev_keys: set[tuple[str, int, str | None]] = set()
|
|
1754
|
+
for it in selected_items[:12]:
|
|
1755
|
+
if it.snippet:
|
|
1756
|
+
if not _guard.allows_file(it.file_path) or not _guard.allows_symbol(it.data.get("symbol")):
|
|
1757
|
+
continue
|
|
1758
|
+
ev_key = (it.file_path, it.start_line, it.data.get("symbol"))
|
|
1759
|
+
if ev_key in seen_ev_keys:
|
|
1760
|
+
continue
|
|
1761
|
+
seen_ev_keys.add(ev_key)
|
|
1762
|
+
src_hash: str | None = None
|
|
1763
|
+
try:
|
|
1764
|
+
row = con.execute("SELECT content_hash FROM files WHERE path=?", (it.file_path,)).fetchone()
|
|
1765
|
+
if row:
|
|
1766
|
+
src_hash = row["content_hash"]
|
|
1767
|
+
except Exception:
|
|
1768
|
+
pass
|
|
1769
|
+
is_stale = it.file_path in stale_paths
|
|
1770
|
+
evidence_out.append(
|
|
1771
|
+
EvidenceRef(
|
|
1772
|
+
file=it.file_path,
|
|
1773
|
+
start_line=it.start_line,
|
|
1774
|
+
end_line=it.end_line,
|
|
1775
|
+
symbol=it.data.get("symbol"),
|
|
1776
|
+
snippet=it.snippet[:600],
|
|
1777
|
+
confidence="LOW" if is_stale else "HIGH",
|
|
1778
|
+
source_hash=src_hash,
|
|
1779
|
+
evidence_status="stale" if is_stale else "current",
|
|
1780
|
+
)
|
|
1781
|
+
)
|
|
1782
|
+
|
|
1783
|
+
# 6. Files (bounded by budget.max_files, aggregating token_estimate across selected items in each file)
|
|
1784
|
+
files_out: list[FileRef] = []
|
|
1785
|
+
file_index_map: dict[str, int] = {}
|
|
1786
|
+
for it in selected_items:
|
|
1787
|
+
if not it.file_path or not _guard.allows_file(it.file_path):
|
|
1788
|
+
continue
|
|
1789
|
+
if it.file_path in file_index_map:
|
|
1790
|
+
existing_idx = file_index_map[it.file_path]
|
|
1791
|
+
existing_ref = files_out[existing_idx]
|
|
1792
|
+
existing_ref.token_estimate += it.estimated_tokens
|
|
1793
|
+
continue
|
|
1794
|
+
if len(files_out) >= budget.max_files:
|
|
1795
|
+
continue
|
|
1796
|
+
file_index_map[it.file_path] = len(files_out)
|
|
1797
|
+
reasons = it.data.get("reasons", [])
|
|
1798
|
+
files_out.append(
|
|
1799
|
+
FileRef(
|
|
1800
|
+
file=it.file_path,
|
|
1801
|
+
score=it.relevance_score,
|
|
1802
|
+
reasons=[
|
|
1803
|
+
RankingReasonModel(code=rs.code, label=rs.label, contribution=rs.contribution)
|
|
1804
|
+
for rs in reasons
|
|
1805
|
+
],
|
|
1806
|
+
snippet=it.snippet[:400],
|
|
1807
|
+
token_estimate=it.estimated_tokens,
|
|
1808
|
+
)
|
|
1809
|
+
)
|
|
1810
|
+
|
|
1811
|
+
# 7. Package-aware bounded packages list & unresolved package dependency preservation
|
|
1812
|
+
packages_out: list[dict[str, object]] = []
|
|
1813
|
+
seen_pkg_ids: set[str] = set()
|
|
1814
|
+
for pid in sorted(ws_info.packages.keys()):
|
|
1815
|
+
p_info = ws_info.packages[pid]
|
|
1816
|
+
if p_info.root_path == "" and len(ws_info.packages) > 1:
|
|
1817
|
+
continue
|
|
1818
|
+
p_dist = pkg_distances.get(pid, 0)
|
|
1819
|
+
if target_pkg and p_dist > effective_pkg_depth and task_spec.intent != "ARCHITECTURE":
|
|
1820
|
+
continue
|
|
1821
|
+
if pid not in seen_pkg_ids:
|
|
1822
|
+
seen_pkg_ids.add(pid)
|
|
1823
|
+
p_dict = p_info.as_dict()
|
|
1824
|
+
p_dict["distance"] = p_dist
|
|
1825
|
+
packages_out.append(p_dict)
|
|
1826
|
+
|
|
1827
|
+
for unres in ws_info.unresolved_dependencies:
|
|
1828
|
+
src_pid = str(unres.get("source_package_id") or "")
|
|
1829
|
+
if not target_pkg or src_pid == target_pkg.package_id:
|
|
1830
|
+
findings_out.append({
|
|
1831
|
+
"type": "UNRESOLVED_PACKAGE_DEPENDENCY",
|
|
1832
|
+
"source": src_pid,
|
|
1833
|
+
"target": str(unres.get("target_name") or ""),
|
|
1834
|
+
"specifier": str(unres.get("specifier") or ""),
|
|
1835
|
+
"status": "UNKNOWN",
|
|
1836
|
+
"reason": f"Dependency '{unres.get('target_name')}' is external or unresolved in workspace",
|
|
1837
|
+
})
|
|
1838
|
+
|
|
1839
|
+
# Check unresolved target resolutions and preserve as UNKNOWN findings
|
|
1840
|
+
assumptions_out = list(task_spec.assumptions)
|
|
1841
|
+
unknowns_out = list(task_spec.unknowns)
|
|
1842
|
+
conflicts_out = list(task_spec.conflicts) if hasattr(task_spec, "conflicts") else []
|
|
1843
|
+
|
|
1844
|
+
for raw_t, t_res in _target_resolutions.items():
|
|
1845
|
+
if t_res.ambiguity_state == "AMBIGUOUS":
|
|
1846
|
+
findings_out.append({
|
|
1847
|
+
"type": "AMBIGUOUS_TARGET",
|
|
1848
|
+
"target": raw_t,
|
|
1849
|
+
"status": "CONFLICT",
|
|
1850
|
+
"alternatives": list(t_res.alternatives),
|
|
1851
|
+
})
|
|
1852
|
+
msg = f"Target '{raw_t}' is ambiguous across multiple candidates."
|
|
1853
|
+
if msg not in conflicts_out:
|
|
1854
|
+
conflicts_out.append(msg)
|
|
1855
|
+
elif t_res.target_type.value == "UNKNOWN":
|
|
1856
|
+
findings_out.append({
|
|
1857
|
+
"type": "UNRESOLVED_TARGET",
|
|
1858
|
+
"target": raw_t,
|
|
1859
|
+
"status": "UNKNOWN",
|
|
1860
|
+
"reason": f"Target '{raw_t}' could not be statically resolved in repository.",
|
|
1861
|
+
})
|
|
1862
|
+
msg = f"Target '{raw_t}' could not be statically resolved."
|
|
1863
|
+
if msg not in unknowns_out:
|
|
1864
|
+
unknowns_out.append(msg)
|
|
1865
|
+
|
|
1866
|
+
# 8. Phase 14: Database, Runtime & Reconciliation Context Assembly
|
|
1867
|
+
database_out: list[dict[str, object]] = []
|
|
1868
|
+
runtime_out: list[dict[str, object]] = []
|
|
1869
|
+
reconciliation_out: list[dict[str, object]] = []
|
|
1870
|
+
|
|
1871
|
+
try:
|
|
1872
|
+
relevant_names: set[str] = set()
|
|
1873
|
+
for t in task_spec.targets:
|
|
1874
|
+
if t:
|
|
1875
|
+
relevant_names.add(t.lower())
|
|
1876
|
+
relevant_names.add(t.split(".")[-1].lower())
|
|
1877
|
+
for s_ref in symbols_out:
|
|
1878
|
+
relevant_names.add(s_ref.symbol.lower())
|
|
1879
|
+
relevant_names.add(s_ref.symbol.split(".")[-1].lower())
|
|
1880
|
+
if s_ref.canonical_id:
|
|
1881
|
+
relevant_names.add(s_ref.canonical_id.lower())
|
|
1882
|
+
for word in task_display_str.lower().replace(",", " ").replace("(", " ").replace(")", " ").split():
|
|
1883
|
+
if len(word) >= 3:
|
|
1884
|
+
relevant_names.add(word)
|
|
1885
|
+
|
|
1886
|
+
db_ent_rows = con.execute(
|
|
1887
|
+
"SELECT canonical_id, entity_type, name, dialect, schema_name, table_name, data_type, "
|
|
1888
|
+
"nullable, primary_key, foreign_key_target, path, start_line, end_line, confidence, evidence_class "
|
|
1889
|
+
"FROM db_entities ORDER BY table_name ASC, entity_type ASC, name ASC LIMIT 80"
|
|
1890
|
+
).fetchall()
|
|
1891
|
+
for drow in db_ent_rows:
|
|
1892
|
+
t_name = str(drow["table_name"] or drow["name"] or "").lower()
|
|
1893
|
+
e_name = str(drow["name"] or "").lower()
|
|
1894
|
+
d_path = str(drow["path"] or "")
|
|
1895
|
+
if (
|
|
1896
|
+
not _guard.allows_file(d_path)
|
|
1897
|
+
or not _guard.allows_symbol(str(drow["name"] or ""))
|
|
1898
|
+
):
|
|
1899
|
+
continue
|
|
1900
|
+
if (
|
|
1901
|
+
t_name in relevant_names
|
|
1902
|
+
or e_name in relevant_names
|
|
1903
|
+
or d_path in selected_paths
|
|
1904
|
+
or any(rn and (rn in t_name or t_name in rn) for rn in relevant_names if len(rn) >= 4)
|
|
1905
|
+
):
|
|
1906
|
+
database_out.append({
|
|
1907
|
+
"record_type": "entity",
|
|
1908
|
+
"canonical_id": str(drow["canonical_id"]),
|
|
1909
|
+
"entity_type": str(drow["entity_type"]),
|
|
1910
|
+
"name": str(drow["name"]),
|
|
1911
|
+
"dialect": str(drow["dialect"]),
|
|
1912
|
+
"schema": str(drow["schema_name"]),
|
|
1913
|
+
"table": str(drow["table_name"] or ""),
|
|
1914
|
+
"data_type": drow["data_type"],
|
|
1915
|
+
"primary_key": bool(drow["primary_key"]),
|
|
1916
|
+
"foreign_key_target": drow["foreign_key_target"],
|
|
1917
|
+
"file": d_path,
|
|
1918
|
+
"start_line": int(drow["start_line"] or 1),
|
|
1919
|
+
"end_line": int(drow["end_line"] or 1),
|
|
1920
|
+
"confidence": str(drow["confidence"]),
|
|
1921
|
+
"evidence_class": str(drow["evidence_class"]),
|
|
1922
|
+
})
|
|
1923
|
+
if len(database_out) >= 20:
|
|
1924
|
+
break
|
|
1925
|
+
|
|
1926
|
+
db_q_rows = con.execute(
|
|
1927
|
+
"SELECT source_symbol, target_table, operation, relationship, path, start_line, end_line, "
|
|
1928
|
+
"confidence, evidence_class, snippet, framework FROM db_queries "
|
|
1929
|
+
"ORDER BY path ASC, start_line ASC LIMIT 50"
|
|
1930
|
+
).fetchall()
|
|
1931
|
+
for qrow in db_q_rows:
|
|
1932
|
+
q_path = str(qrow["path"] or "")
|
|
1933
|
+
q_sym = str(qrow["source_symbol"] or "")
|
|
1934
|
+
q_tbl = str(qrow["target_table"] or "").lower()
|
|
1935
|
+
if not _guard.allows_file(q_path) or not _guard.allows_symbol(q_sym):
|
|
1936
|
+
continue
|
|
1937
|
+
if (
|
|
1938
|
+
q_path in selected_paths
|
|
1939
|
+
or q_tbl in relevant_names
|
|
1940
|
+
or q_sym.lower() in relevant_names
|
|
1941
|
+
or q_sym.split("::")[-1].split(".")[-1].lower() in relevant_names
|
|
1942
|
+
):
|
|
1943
|
+
database_out.append({
|
|
1944
|
+
"record_type": "query",
|
|
1945
|
+
"source_symbol": q_sym,
|
|
1946
|
+
"target_table": str(qrow["target_table"]),
|
|
1947
|
+
"operation": str(qrow["operation"]),
|
|
1948
|
+
"relationship": str(qrow["relationship"]),
|
|
1949
|
+
"framework": str(qrow["framework"] or ""),
|
|
1950
|
+
"file": q_path,
|
|
1951
|
+
"start_line": int(qrow["start_line"] or 1),
|
|
1952
|
+
"end_line": int(qrow["end_line"] or 1),
|
|
1953
|
+
"confidence": str(qrow["confidence"]),
|
|
1954
|
+
"evidence_class": str(qrow["evidence_class"]),
|
|
1955
|
+
"snippet": redact_secrets(str(qrow["snippet"] or "")[:240]),
|
|
1956
|
+
})
|
|
1957
|
+
if len(database_out) >= 30:
|
|
1958
|
+
break
|
|
1959
|
+
except Exception:
|
|
1960
|
+
pass
|
|
1961
|
+
|
|
1962
|
+
try:
|
|
1963
|
+
rt_rows = con.execute(
|
|
1964
|
+
"SELECT source, target, relationship, observation_count, first_seen, last_seen, "
|
|
1965
|
+
"runtime_generation, confidence, evidence_class, sample_trace_id "
|
|
1966
|
+
"FROM runtime_edges ORDER BY observation_count DESC, source ASC, target ASC LIMIT 25"
|
|
1967
|
+
).fetchall()
|
|
1968
|
+
for rrow in rt_rows:
|
|
1969
|
+
runtime_out.append({
|
|
1970
|
+
"source": str(rrow["source"]),
|
|
1971
|
+
"target": str(rrow["target"]),
|
|
1972
|
+
"relationship": str(rrow["relationship"]),
|
|
1973
|
+
"observation_count": int(rrow["observation_count"] or 1),
|
|
1974
|
+
"first_seen": str(rrow["first_seen"] or ""),
|
|
1975
|
+
"last_seen": str(rrow["last_seen"] or ""),
|
|
1976
|
+
"runtime_generation": int(rrow["runtime_generation"] or 1),
|
|
1977
|
+
"confidence": str(rrow["confidence"] or "HIGH"),
|
|
1978
|
+
"evidence_class": "RUNTIME_OBSERVED",
|
|
1979
|
+
"sample_trace_id": str(rrow["sample_trace_id"] or ""),
|
|
1980
|
+
})
|
|
1981
|
+
if runtime_out:
|
|
1982
|
+
from codegraph.runtime.reconciliation import reconcile_static_runtime
|
|
1983
|
+
rec_res = reconcile_static_runtime(con, repository)
|
|
1984
|
+
for item_rec in rec_res.get("items", [])[:20]:
|
|
1985
|
+
reconciliation_out.append(dict(item_rec))
|
|
1986
|
+
except Exception:
|
|
1987
|
+
pass
|
|
1988
|
+
|
|
1989
|
+
uncertainties: list[str] = []
|
|
1990
|
+
if freshness_report.status != FreshnessStatus.FRESH:
|
|
1991
|
+
uncertainties.append(f"Index is {freshness_report.status.value}: {freshness_report.detail}")
|
|
1992
|
+
if stale_paths & selected_paths:
|
|
1993
|
+
uncertainties.append(f"Evidence from {len(stale_paths & selected_paths)} file(s) may be stale.")
|
|
1994
|
+
if not candidate_dicts:
|
|
1995
|
+
uncertainties.append("No lexical or graph matches found. Results may be incomplete.")
|
|
1996
|
+
|
|
1997
|
+
timing.serialization_ms = (time.perf_counter() - t_serial_start) * 1000
|
|
1998
|
+
timing.total_ms = timer.elapsed_ms
|
|
1999
|
+
|
|
2000
|
+
exec_meta = ExecutionMetadata(
|
|
2001
|
+
latency_ms=timer.elapsed_ms,
|
|
2002
|
+
cache_hit=False,
|
|
2003
|
+
index_generation=gen,
|
|
2004
|
+
candidate_count=budget.candidate_tokens,
|
|
2005
|
+
selected_count=budget.selected_tokens,
|
|
2006
|
+
mode=mode_upper,
|
|
2007
|
+
timing=timing,
|
|
2008
|
+
)
|
|
2009
|
+
exec_dict = exec_meta.as_dict()
|
|
2010
|
+
exec_dict["excluded_count"] = excluded_artifacts_count
|
|
2011
|
+
exec_dict["excluded_artifact_classes"] = sorted(excluded_artifact_classes)
|
|
2012
|
+
exec_dict["artifact_inclusions"] = artifact_inclusions
|
|
2013
|
+
exec_dict["package_context"] = target_pkg.as_dict() if target_pkg else None
|
|
2014
|
+
exec_dict["evidence_compression"] = compression_stats
|
|
2015
|
+
exec_dict["tool_efficiency"] = {
|
|
2016
|
+
"tool_calls": 1,
|
|
2017
|
+
"internal_queries_executed": len(queries_to_run),
|
|
2018
|
+
"consolidated_operations": [
|
|
2019
|
+
"target_resolution",
|
|
2020
|
+
"hybrid_search",
|
|
2021
|
+
"graph_traversal",
|
|
2022
|
+
"route_matching",
|
|
2023
|
+
"database_intelligence",
|
|
2024
|
+
"runtime_reconciliation",
|
|
2025
|
+
"test_discovery",
|
|
2026
|
+
"package_boundary_filtering",
|
|
2027
|
+
"budget_optimization",
|
|
2028
|
+
],
|
|
2029
|
+
}
|
|
2030
|
+
exec_dict["resource"] = {
|
|
2031
|
+
"profile": governor.policy.profile.value,
|
|
2032
|
+
"pressure": governor.get_pressure().value,
|
|
2033
|
+
"activity": governor.get_activity_mode().value,
|
|
2034
|
+
"estimated_memory_mb": get_process_memory_mb(),
|
|
2035
|
+
}
|
|
2036
|
+
if explain:
|
|
2037
|
+
exec_dict["rejections"] = list(budget.rejections)
|
|
2038
|
+
exec_dict["retrieval_plan"] = retrieval_plan.as_dict()
|
|
2039
|
+
exec_dict["explain"] = {
|
|
2040
|
+
"task_spec": task_spec.as_dict(),
|
|
2041
|
+
"retrieval_plan": retrieval_plan.as_dict(),
|
|
2042
|
+
"stage1_candidate_count": stage1_candidate_count,
|
|
2043
|
+
"stage2_candidate_count": len(candidate_dicts),
|
|
2044
|
+
"coverage_layers": {
|
|
2045
|
+
layer: sum(1 for it in selected_items if it.coverage_layer == layer)
|
|
2046
|
+
for layer in set(it.coverage_layer for it in selected_items)
|
|
2047
|
+
},
|
|
2048
|
+
"why_selected": {
|
|
2049
|
+
it.item_id: it.why_selected for it in selected_items
|
|
2050
|
+
},
|
|
2051
|
+
"rejections": list(budget.rejections),
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
coverage_dict: dict[str, object] = {
|
|
2055
|
+
"coverage_score": round(budget.coverage_score, 4),
|
|
2056
|
+
"required_dimensions": list(_retrieval_policy.required_dimensions),
|
|
2057
|
+
"covered_dimensions": list(budget.covered_dimensions),
|
|
2058
|
+
"missing_dimensions": list(budget.missing_dimensions),
|
|
2059
|
+
"reason_codes": {
|
|
2060
|
+
it.canonical_id or str(it.data.get("symbol") or it.file_path): it.reason_code
|
|
2061
|
+
for it in selected_items
|
|
2062
|
+
},
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
metadata_dict: dict[str, object] = {
|
|
2066
|
+
"schema_version": SCHEMA_VERSION,
|
|
2067
|
+
"repository": str(repository),
|
|
2068
|
+
"repository_generation": gen,
|
|
2069
|
+
"freshness": freshness_report.status.value,
|
|
2070
|
+
"mode": mode_upper,
|
|
2071
|
+
"intent": str(caller_raw_intent) if caller_raw_intent else task_spec.intent.lower(),
|
|
2072
|
+
"selected_tokens": budget.selected_tokens,
|
|
2073
|
+
"candidate_tokens": budget.candidate_tokens,
|
|
2074
|
+
"selected_files": len(files_out),
|
|
2075
|
+
"selected_lines": budget.selected_lines,
|
|
2076
|
+
"coverage_score": round(budget.coverage_score, 4),
|
|
2077
|
+
"truncated": budget.truncated,
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
target_dict: dict[str, object] = {
|
|
2081
|
+
"raw_targets": list(task_spec.targets),
|
|
2082
|
+
"resolutions": {
|
|
2083
|
+
k: {
|
|
2084
|
+
"target_type": v.target_type.value,
|
|
2085
|
+
"canonical_id": v.canonical_id,
|
|
2086
|
+
"qualified_name": v.qualified_name,
|
|
2087
|
+
"file_path": v.file_path,
|
|
2088
|
+
"confidence": v.confidence,
|
|
2089
|
+
"ambiguity_state": v.ambiguity_state,
|
|
2090
|
+
}
|
|
2091
|
+
for k, v in _target_resolutions.items()
|
|
2092
|
+
},
|
|
2093
|
+
"package": target_pkg.as_dict() if target_pkg else None,
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
git_impact_dict: dict[str, object] = {
|
|
2097
|
+
"recent_changes": git_changes_out,
|
|
2098
|
+
"modified_files_count": len(recent_paths),
|
|
2099
|
+
"affected_package": target_pkg.package_id if target_pkg else None,
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
packet = ContextPacket(
|
|
2103
|
+
schema_version=SCHEMA_VERSION,
|
|
2104
|
+
task=task_display_str,
|
|
2105
|
+
intent=str(caller_raw_intent) if caller_raw_intent else task_spec.intent.lower(),
|
|
2106
|
+
repository=str(repository),
|
|
2107
|
+
indexed_commit=freshness_report.indexed_commit,
|
|
2108
|
+
current_commit=freshness_report.current_commit,
|
|
2109
|
+
freshness=freshness_report.status.value,
|
|
2110
|
+
freshness_detail=freshness_report.detail,
|
|
2111
|
+
repository_generation=gen,
|
|
2112
|
+
task_spec=task_spec.as_dict(),
|
|
2113
|
+
task_fingerprint=getattr(retrieval_plan, "task_fingerprint", "") or task_spec.fingerprint(),
|
|
2114
|
+
budget=budget.as_dict(),
|
|
2115
|
+
execution=exec_dict,
|
|
2116
|
+
mode=mode_upper,
|
|
2117
|
+
entry_points=[
|
|
2118
|
+
r for r in entry_points_out
|
|
2119
|
+
if any(sp in str(r.get("file", "")) for sp in selected_paths)
|
|
2120
|
+
][:5] if (max_tokens <= 4000 and entry_points_out) else entry_points_out[:10],
|
|
2121
|
+
framework_facts=[
|
|
2122
|
+
f for f in framework_facts_out
|
|
2123
|
+
if any(sp in str(f.get("file", "")) for sp in selected_paths)
|
|
2124
|
+
][:10] if (max_tokens <= 4000 and framework_facts_out) else framework_facts_out[:20],
|
|
2125
|
+
architecture=architecture_out[:5] if max_tokens <= 4000 else architecture_out,
|
|
2126
|
+
git_changes=git_changes_out[:5] if max_tokens <= 4000 else git_changes_out,
|
|
2127
|
+
git_facts=git_changes_out[:5] if max_tokens <= 4000 else git_changes_out,
|
|
2128
|
+
assumptions=assumptions_out,
|
|
2129
|
+
unknowns=unknowns_out,
|
|
2130
|
+
conflicts=conflicts_out,
|
|
2131
|
+
metadata=metadata_dict,
|
|
2132
|
+
target=target_dict,
|
|
2133
|
+
routes=[
|
|
2134
|
+
r for r in entry_points_out
|
|
2135
|
+
if any(sp in str(r.get("file", "")) for sp in selected_paths)
|
|
2136
|
+
][:5] if (max_tokens <= 4000 and entry_points_out) else entry_points_out[:10],
|
|
2137
|
+
packages=packages_out[:5] if max_tokens <= 4000 else packages_out,
|
|
2138
|
+
git_impact=git_impact_dict,
|
|
2139
|
+
findings=findings_out[:10] if max_tokens <= 4000 else findings_out,
|
|
2140
|
+
coverage=coverage_dict,
|
|
2141
|
+
database=[
|
|
2142
|
+
d for d in database_out
|
|
2143
|
+
if any(sp in str(d.get("file", "")) for sp in selected_paths)
|
|
2144
|
+
][:5] if (max_tokens <= 4000 and database_out) else database_out[:15],
|
|
2145
|
+
runtime=runtime_out,
|
|
2146
|
+
reconciliation=reconciliation_out,
|
|
2147
|
+
symbols=symbols_out,
|
|
2148
|
+
relationships=relationships_out,
|
|
2149
|
+
tests=tests_out,
|
|
2150
|
+
files=files_out,
|
|
2151
|
+
evidence=evidence_out,
|
|
2152
|
+
uncertainties=uncertainties,
|
|
2153
|
+
candidate_tokens=budget.candidate_tokens,
|
|
2154
|
+
selected_tokens=budget.selected_tokens,
|
|
2155
|
+
selected_lines=budget.selected_lines,
|
|
2156
|
+
coverage_score=round(budget.coverage_score, 4),
|
|
2157
|
+
truncated=budget.truncated,
|
|
2158
|
+
candidate_token_estimate=budget.candidate_tokens,
|
|
2159
|
+
selected_token_estimate=budget.selected_tokens,
|
|
2160
|
+
context_reduction_pct=round(budget.reduction_ratio * 100, 1),
|
|
2161
|
+
selected_files=sorted(selected_paths),
|
|
2162
|
+
discarded_files=sorted(discarded_paths),
|
|
2163
|
+
)
|
|
2164
|
+
|
|
2165
|
+
store_cached_context_packet(con, cache_key, packet.as_dict())
|
|
2166
|
+
|
|
2167
|
+
get_global_metrics().record(
|
|
2168
|
+
OperationMetric(
|
|
2169
|
+
request_id=req_id,
|
|
2170
|
+
tool_or_op="get_context",
|
|
2171
|
+
latency_ms=timer.elapsed_ms,
|
|
2172
|
+
index_generation=gen,
|
|
2173
|
+
cache_hit=False,
|
|
2174
|
+
candidate_count=budget.candidate_tokens,
|
|
2175
|
+
selected_count=budget.selected_tokens,
|
|
2176
|
+
estimated_tokens=budget.selected_tokens,
|
|
2177
|
+
task_intent=task_spec.intent,
|
|
2178
|
+
freshness=freshness_report.status.value,
|
|
2179
|
+
)
|
|
2180
|
+
)
|
|
2181
|
+
|
|
2182
|
+
return packet
|