codegraph-engine 2.1.2__py3-none-any.whl → 2.1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codegraph/__init__.py +1 -1
- codegraph/agent_brain.py +1961 -0
- codegraph/agent_capabilities.py +3500 -0
- codegraph/agent_rules.py +530 -0
- codegraph/architecture.py +48 -5
- codegraph/binding_resolver.py +616 -0
- codegraph/cli.py +345 -5
- codegraph/config.py +2 -0
- codegraph/context.py +845 -93
- codegraph/database/__init__.py +58 -0
- codegraph/database/extractor.py +2956 -0
- codegraph/database/interrogation.py +1085 -0
- codegraph/database/models.py +310 -0
- codegraph/epistemic.py +95 -0
- codegraph/errors.py +7 -0
- codegraph/evidence/citations.py +22 -1
- codegraph/evidence_contract.py +455 -0
- codegraph/frameworks.py +372 -11
- codegraph/git.py +92 -10
- codegraph/graph/models.py +15 -0
- codegraph/graph/traversal.py +680 -121
- codegraph/indexing/__init__.py +15 -1
- codegraph/indexing/classifier.py +343 -58
- codegraph/indexing/indexer.py +1515 -141
- codegraph/indexing/models.py +75 -0
- codegraph/indexing/parser.py +1843 -70
- codegraph/indexing/scanner.py +4 -1
- codegraph/indexing/telemetry.py +326 -0
- codegraph/installer.py +1652 -0
- codegraph/interrogation.py +708 -122
- codegraph/mcp/server.py +525 -107
- codegraph/mcp_diagnostics.py +879 -0
- codegraph/models.py +14 -0
- codegraph/monorepo.py +922 -0
- codegraph/observability.py +113 -2
- codegraph/optimizer.py +647 -42
- codegraph/ranking.py +61 -16
- codegraph/resolver.py +2006 -77
- codegraph/resources/monitor.py +1 -1
- codegraph/retrieval_policy.py +96 -17
- codegraph/route_composer.py +363 -0
- codegraph/runtime/__init__.py +32 -0
- codegraph/runtime/ingestor.py +672 -0
- codegraph/runtime/models.py +130 -0
- codegraph/runtime/reconciliation.py +492 -0
- codegraph/search/__init__.py +2 -0
- codegraph/search/hybrid.py +572 -53
- codegraph/security/__init__.py +50 -2
- codegraph/security/paths.py +162 -8
- codegraph/security/redaction.py +633 -0
- codegraph/semantic_decorators.py +341 -0
- codegraph/target_resolver.py +6 -6
- codegraph/tool_selection_eval.py +1158 -0
- codegraph_engine-2.1.7.dist-info/METADATA +617 -0
- codegraph_engine-2.1.7.dist-info/RECORD +84 -0
- {codegraph_engine-2.1.2.dist-info → codegraph_engine-2.1.7.dist-info}/licenses/LICENSE +1 -1
- codegraph_engine-2.1.2.dist-info/METADATA +0 -336
- codegraph_engine-2.1.2.dist-info/RECORD +0 -63
- {codegraph_engine-2.1.2.dist-info → codegraph_engine-2.1.7.dist-info}/WHEEL +0 -0
- {codegraph_engine-2.1.2.dist-info → codegraph_engine-2.1.7.dist-info}/entry_points.txt +0 -0
- {codegraph_engine-2.1.2.dist-info → codegraph_engine-2.1.7.dist-info}/top_level.txt +0 -0
codegraph/context.py
CHANGED
|
@@ -44,8 +44,12 @@ from codegraph.observability import (
|
|
|
44
44
|
get_global_metrics,
|
|
45
45
|
)
|
|
46
46
|
from codegraph.optimizer import (
|
|
47
|
+
HARD_MAX_LINES,
|
|
47
48
|
CandidateContextItem,
|
|
49
|
+
ContextBudgetSpec,
|
|
50
|
+
compress_relationships,
|
|
48
51
|
optimize_context_budget,
|
|
52
|
+
select_bounded_snippet,
|
|
49
53
|
)
|
|
50
54
|
from codegraph.planner import RetrievalPlan, build_retrieval_plan
|
|
51
55
|
from codegraph.query_expansion import QueryExpansion, expand_query_terms, get_search_queries
|
|
@@ -92,6 +96,8 @@ class SymbolRef(BaseModel):
|
|
|
92
96
|
score: float
|
|
93
97
|
reasons: list[RankingReasonModel] = []
|
|
94
98
|
canonical_id: str | None = None
|
|
99
|
+
reason_code: str = ""
|
|
100
|
+
package_id: str | None = None
|
|
95
101
|
|
|
96
102
|
|
|
97
103
|
class Relationship(BaseModel):
|
|
@@ -102,7 +108,19 @@ class Relationship(BaseModel):
|
|
|
102
108
|
file: str | None = None
|
|
103
109
|
start_line: int | None = None
|
|
104
110
|
evidence: str = ""
|
|
105
|
-
status: 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)
|
|
106
124
|
|
|
107
125
|
|
|
108
126
|
class TestRef(BaseModel):
|
|
@@ -158,6 +176,18 @@ class ContextPacket(BaseModel):
|
|
|
158
176
|
unknowns: list[str] = []
|
|
159
177
|
conflicts: list[str] = []
|
|
160
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
|
+
|
|
161
191
|
# Knowledge
|
|
162
192
|
facts: list[dict[str, object]] = field(default_factory=list)
|
|
163
193
|
symbols: list[SymbolRef] = []
|
|
@@ -167,7 +197,12 @@ class ContextPacket(BaseModel):
|
|
|
167
197
|
evidence: list[EvidenceRef] = []
|
|
168
198
|
uncertainties: list[str] = []
|
|
169
199
|
|
|
170
|
-
# Token budget
|
|
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
|
|
171
206
|
candidate_token_estimate: int = 0
|
|
172
207
|
selected_token_estimate: int = 0
|
|
173
208
|
context_reduction_pct: float = 0.0
|
|
@@ -179,6 +214,9 @@ class ContextPacket(BaseModel):
|
|
|
179
214
|
def as_dict(self) -> dict[str, object]:
|
|
180
215
|
return self.model_dump()
|
|
181
216
|
|
|
217
|
+
def to_dict(self) -> dict[str, object]:
|
|
218
|
+
return self.model_dump()
|
|
219
|
+
|
|
182
220
|
|
|
183
221
|
# ---------------------------------------------------------------------------
|
|
184
222
|
# Token estimation helper
|
|
@@ -191,6 +229,67 @@ def _token_estimate(text: str) -> int:
|
|
|
191
229
|
return max(1, len(text) // _CHARS_PER_TOKEN)
|
|
192
230
|
|
|
193
231
|
|
|
232
|
+
def _resolve_task_or_query_input(
|
|
233
|
+
task: str | TaskSpec | dict[str, Any] | None = "",
|
|
234
|
+
query: str | TaskSpec | dict[str, Any] | None = "",
|
|
235
|
+
) -> str | TaskSpec | dict[str, Any]:
|
|
236
|
+
"""Resolve canonical `query` / `task` inputs with strict conflict validation."""
|
|
237
|
+
def _is_nonempty(val: Any) -> bool:
|
|
238
|
+
if val is None:
|
|
239
|
+
return False
|
|
240
|
+
if isinstance(val, str):
|
|
241
|
+
return bool(val.strip())
|
|
242
|
+
if isinstance(val, dict):
|
|
243
|
+
return bool(val)
|
|
244
|
+
return True
|
|
245
|
+
|
|
246
|
+
has_task = _is_nonempty(task)
|
|
247
|
+
has_query = _is_nonempty(query)
|
|
248
|
+
|
|
249
|
+
if not has_task and not has_query:
|
|
250
|
+
raise ValueError("Either 'query' or 'task' must be provided to get_context (non-empty string or TaskSpec).")
|
|
251
|
+
|
|
252
|
+
if has_task and has_query:
|
|
253
|
+
if isinstance(task, str) and isinstance(query, str):
|
|
254
|
+
if task.strip() != query.strip():
|
|
255
|
+
raise ValueError(
|
|
256
|
+
f"Conflicting 'query' ({query.strip()!r}) and 'task' ({task.strip()!r}) provided; "
|
|
257
|
+
"pass one or identical values."
|
|
258
|
+
)
|
|
259
|
+
return query.strip()
|
|
260
|
+
if isinstance(task, dict) and isinstance(query, str):
|
|
261
|
+
dict_prompt = str(task.get("raw_prompt") or task.get("goal") or task.get("task") or task.get("query") or "").strip()
|
|
262
|
+
if dict_prompt and dict_prompt != query.strip():
|
|
263
|
+
raise ValueError(
|
|
264
|
+
f"Conflicting 'query' ({query.strip()!r}) and 'task' prompt ({dict_prompt!r}) provided."
|
|
265
|
+
)
|
|
266
|
+
if not dict_prompt:
|
|
267
|
+
merged = dict(task)
|
|
268
|
+
merged["raw_prompt"] = query.strip()
|
|
269
|
+
return merged
|
|
270
|
+
return task
|
|
271
|
+
if isinstance(query, dict) and isinstance(task, str):
|
|
272
|
+
dict_prompt = str(query.get("raw_prompt") or query.get("goal") or query.get("task") or query.get("query") or "").strip()
|
|
273
|
+
if dict_prompt and dict_prompt != task.strip():
|
|
274
|
+
raise ValueError(
|
|
275
|
+
f"Conflicting 'query' prompt ({dict_prompt!r}) and 'task' ({task.strip()!r}) provided."
|
|
276
|
+
)
|
|
277
|
+
if not dict_prompt:
|
|
278
|
+
merged = dict(query)
|
|
279
|
+
merged["raw_prompt"] = task.strip()
|
|
280
|
+
return merged
|
|
281
|
+
return query
|
|
282
|
+
if task != query:
|
|
283
|
+
raise ValueError("Conflicting 'query' and 'task' arguments provided to get_context.")
|
|
284
|
+
return task # type: ignore[return-value]
|
|
285
|
+
|
|
286
|
+
resolved = query if has_query else task
|
|
287
|
+
if isinstance(resolved, str):
|
|
288
|
+
return resolved.strip()
|
|
289
|
+
assert resolved is not None
|
|
290
|
+
return resolved
|
|
291
|
+
|
|
292
|
+
|
|
194
293
|
# ---------------------------------------------------------------------------
|
|
195
294
|
# Main Context Compiler
|
|
196
295
|
# ---------------------------------------------------------------------------
|
|
@@ -199,25 +298,43 @@ def _token_estimate(text: str) -> int:
|
|
|
199
298
|
def get_context(
|
|
200
299
|
con: sqlite3.Connection,
|
|
201
300
|
repository: Path,
|
|
202
|
-
task: str | TaskSpec | dict[str, Any],
|
|
301
|
+
task: str | TaskSpec | dict[str, Any] = "",
|
|
203
302
|
intent: Intent | str | None = None,
|
|
204
303
|
max_tokens: int = 20_000,
|
|
205
304
|
top_k: int = 15,
|
|
206
305
|
plan: RetrievalPlan | dict[str, Any] | None = None,
|
|
207
306
|
mode: str = "BALANCED", # FAST | BALANCED | DEEP
|
|
208
307
|
explain: bool = False,
|
|
308
|
+
include_generated: bool = False,
|
|
309
|
+
include_vendor: bool = False,
|
|
310
|
+
include_bundles: bool = False,
|
|
311
|
+
include_build_artifacts: bool = False,
|
|
312
|
+
max_symbols: int = 25,
|
|
313
|
+
max_relationships: int = 30,
|
|
314
|
+
max_files: int = 15,
|
|
315
|
+
max_lines: int = 500,
|
|
316
|
+
max_depth: int | None = None,
|
|
317
|
+
max_package_depth: int | None = None,
|
|
318
|
+
query: str | TaskSpec | dict[str, Any] = "",
|
|
209
319
|
) -> ContextPacket:
|
|
210
|
-
"""Compile a deterministic, verified ContextPacket for the given task and intent."""
|
|
320
|
+
"""Compile a deterministic, verified ContextPacket for the given query/task and intent."""
|
|
321
|
+
effective_task = _resolve_task_or_query_input(task=task, query=query)
|
|
322
|
+
if max_tokens <= 0 or max_files <= 0 or max_lines <= 0:
|
|
323
|
+
raise ValueError("max_tokens, max_files, and max_lines must be positive integers (> 0).")
|
|
211
324
|
governor = get_global_governor()
|
|
212
325
|
coalescer = get_global_coalescer()
|
|
213
|
-
flight_key =
|
|
326
|
+
flight_key = (
|
|
327
|
+
f"{repository}:{effective_task}:{intent}:{max_tokens}:{top_k}:{mode}:{explain}:"
|
|
328
|
+
f"{include_generated}:{include_vendor}:{include_bundles}:{include_build_artifacts}:"
|
|
329
|
+
f"{max_symbols}:{max_relationships}:{max_files}:{max_lines}:{max_depth}:{max_package_depth}"
|
|
330
|
+
)
|
|
214
331
|
|
|
215
332
|
def _execute() -> ContextPacket:
|
|
216
333
|
with governor.task_scope(TaskPriority.INTERACTIVE_HIGH):
|
|
217
334
|
return _get_context_impl(
|
|
218
335
|
con=con,
|
|
219
336
|
repository=repository,
|
|
220
|
-
task=
|
|
337
|
+
task=effective_task,
|
|
221
338
|
intent=intent,
|
|
222
339
|
max_tokens=max_tokens,
|
|
223
340
|
top_k=top_k,
|
|
@@ -225,6 +342,16 @@ def get_context(
|
|
|
225
342
|
mode=mode,
|
|
226
343
|
explain=explain,
|
|
227
344
|
governor=governor,
|
|
345
|
+
include_generated=include_generated,
|
|
346
|
+
include_vendor=include_vendor,
|
|
347
|
+
include_bundles=include_bundles,
|
|
348
|
+
include_build_artifacts=include_build_artifacts,
|
|
349
|
+
max_symbols=max_symbols,
|
|
350
|
+
max_relationships=max_relationships,
|
|
351
|
+
max_files=max_files,
|
|
352
|
+
max_lines=max_lines,
|
|
353
|
+
max_depth=max_depth,
|
|
354
|
+
max_package_depth=max_package_depth,
|
|
228
355
|
)
|
|
229
356
|
|
|
230
357
|
return coalescer.coalesce(flight_key, _execute)
|
|
@@ -241,6 +368,16 @@ def _get_context_impl(
|
|
|
241
368
|
mode: str = "BALANCED", # FAST | BALANCED | DEEP
|
|
242
369
|
explain: bool = False,
|
|
243
370
|
governor: Any = None,
|
|
371
|
+
include_generated: bool = False,
|
|
372
|
+
include_vendor: bool = False,
|
|
373
|
+
include_bundles: bool = False,
|
|
374
|
+
include_build_artifacts: bool = False,
|
|
375
|
+
max_symbols: int = 25,
|
|
376
|
+
max_relationships: int = 30,
|
|
377
|
+
max_files: int = 15,
|
|
378
|
+
max_lines: int = 500,
|
|
379
|
+
max_depth: int | None = None,
|
|
380
|
+
max_package_depth: int | None = None,
|
|
244
381
|
) -> ContextPacket:
|
|
245
382
|
if governor is None:
|
|
246
383
|
governor = get_global_governor()
|
|
@@ -307,11 +444,22 @@ def _get_context_impl(
|
|
|
307
444
|
)
|
|
308
445
|
|
|
309
446
|
gen = index_generation(con)
|
|
447
|
+
runtime_edge_count = 0
|
|
448
|
+
try:
|
|
449
|
+
row_rt = con.execute("SELECT count(*), COALESCE(MAX(runtime_generation), 0) FROM runtime_edges").fetchone()
|
|
450
|
+
if row_rt:
|
|
451
|
+
runtime_edge_count = int(row_rt[0] or 0) + int(row_rt[1] or 0) * 1000
|
|
452
|
+
except Exception:
|
|
453
|
+
pass
|
|
310
454
|
cache_key = compute_context_cache_key(
|
|
311
455
|
task_spec=task_spec,
|
|
312
456
|
repository_generation=gen,
|
|
313
457
|
max_tokens=max_tokens,
|
|
314
|
-
extra_config=
|
|
458
|
+
extra_config=(
|
|
459
|
+
f"{caller_raw_intent or ''}:{mode_upper}:{top_k}:"
|
|
460
|
+
f"{include_generated}:{include_vendor}:{include_bundles}:{include_build_artifacts}:"
|
|
461
|
+
f"{max_symbols}:{max_relationships}:{max_files}:{max_lines}:{max_depth}:{max_package_depth}:{runtime_edge_count}"
|
|
462
|
+
),
|
|
315
463
|
)
|
|
316
464
|
|
|
317
465
|
# 2. Check Cache
|
|
@@ -329,7 +477,13 @@ def _get_context_impl(
|
|
|
329
477
|
mode=mode_upper,
|
|
330
478
|
timing=timing,
|
|
331
479
|
)
|
|
480
|
+
old_exec = cached_data.get("execution", {}) if isinstance(cached_data, dict) else {}
|
|
332
481
|
packet.execution = exec_meta.as_dict()
|
|
482
|
+
packet.execution["excluded_count"] = old_exec.get("excluded_count", 0)
|
|
483
|
+
packet.execution["excluded_artifact_classes"] = old_exec.get("excluded_artifact_classes", [])
|
|
484
|
+
packet.execution["artifact_inclusions"] = old_exec.get("artifact_inclusions", [])
|
|
485
|
+
packet.execution["package_context"] = old_exec.get("package_context")
|
|
486
|
+
packet.execution["tool_efficiency"] = old_exec.get("tool_efficiency", {"tool_calls": 1, "cache_hit": True})
|
|
333
487
|
packet.execution["resource"] = {
|
|
334
488
|
"profile": governor.policy.profile.value,
|
|
335
489
|
"pressure": governor.get_pressure().value,
|
|
@@ -442,13 +596,31 @@ def _get_context_impl(
|
|
|
442
596
|
c.row_factory = sqlite3.Row
|
|
443
597
|
try:
|
|
444
598
|
for query in queries:
|
|
445
|
-
for item in search(
|
|
599
|
+
for item in search(
|
|
600
|
+
c,
|
|
601
|
+
query,
|
|
602
|
+
top_k=effective_top_k,
|
|
603
|
+
include_generated=include_generated,
|
|
604
|
+
include_vendor=include_vendor,
|
|
605
|
+
include_bundles=include_bundles,
|
|
606
|
+
include_build_artifacts=include_build_artifacts,
|
|
607
|
+
include_text_files=False,
|
|
608
|
+
):
|
|
446
609
|
items.append(item.as_dict())
|
|
447
610
|
finally:
|
|
448
611
|
c.close()
|
|
449
612
|
else:
|
|
450
613
|
for query in queries:
|
|
451
|
-
for item in search(
|
|
614
|
+
for item in search(
|
|
615
|
+
con,
|
|
616
|
+
query,
|
|
617
|
+
top_k=effective_top_k,
|
|
618
|
+
include_generated=include_generated,
|
|
619
|
+
include_vendor=include_vendor,
|
|
620
|
+
include_bundles=include_bundles,
|
|
621
|
+
include_build_artifacts=include_build_artifacts,
|
|
622
|
+
include_text_files=False,
|
|
623
|
+
):
|
|
452
624
|
items.append(item.as_dict())
|
|
453
625
|
return items
|
|
454
626
|
|
|
@@ -564,6 +736,7 @@ def _get_context_impl(
|
|
|
564
736
|
"start_line": r_line,
|
|
565
737
|
"end_line": r_line,
|
|
566
738
|
"score": 0.95,
|
|
739
|
+
"relationship": "HANDLED_BY",
|
|
567
740
|
"snippet": f"Endpoint: {ep_id} handled by {r['handler_name']}",
|
|
568
741
|
}
|
|
569
742
|
)
|
|
@@ -576,6 +749,7 @@ def _get_context_impl(
|
|
|
576
749
|
start_line=r_line,
|
|
577
750
|
evidence=f"Route definition for {ep_id}",
|
|
578
751
|
status="FACT",
|
|
752
|
+
evidence_class="FRAMEWORK_VERIFIED",
|
|
579
753
|
)
|
|
580
754
|
route_relationships.append(route_rel)
|
|
581
755
|
entry_points_out.append(
|
|
@@ -665,20 +839,121 @@ def _get_context_impl(
|
|
|
665
839
|
_seen_seeds.add(s_sym)
|
|
666
840
|
ordered_seeds.append(s_sym)
|
|
667
841
|
|
|
842
|
+
effective_max_depth = max_depth if max_depth is not None else _retrieval_policy.max_graph_depth
|
|
843
|
+
effective_pkg_depth = max_package_depth if max_package_depth is not None else _retrieval_policy.max_package_depth
|
|
844
|
+
|
|
668
845
|
# Bounded seed policy
|
|
669
846
|
graph_seed_policy = GraphSeedPolicy(
|
|
670
847
|
source_type="EXPLICIT_TARGET",
|
|
671
848
|
min_confidence="HIGH",
|
|
672
849
|
max_seeds=max_graph_syms,
|
|
673
|
-
max_depth=
|
|
850
|
+
max_depth=effective_max_depth,
|
|
674
851
|
allowed_edges=_retrieval_policy.allowed_relationship_types,
|
|
675
852
|
)
|
|
676
853
|
|
|
677
|
-
for
|
|
854
|
+
# Monorepo / Package boundary detection early for package-aware limiting
|
|
855
|
+
from codegraph.monorepo import detect_workspace
|
|
856
|
+
ws_info = detect_workspace(repository, con=con)
|
|
857
|
+
target_pkg = None
|
|
858
|
+
for t in task_spec.targets:
|
|
859
|
+
pkg = ws_info.get_package_for_file(t)
|
|
860
|
+
if pkg and pkg.root_path != "":
|
|
861
|
+
target_pkg = pkg
|
|
862
|
+
break
|
|
863
|
+
if not target_pkg:
|
|
864
|
+
for t in task_spec.targets:
|
|
865
|
+
pkg = ws_info.get_package_for_file(t)
|
|
866
|
+
if pkg:
|
|
867
|
+
target_pkg = pkg
|
|
868
|
+
break
|
|
869
|
+
if not target_pkg and ordered_seeds:
|
|
870
|
+
for s in ordered_seeds:
|
|
871
|
+
s_row = con.execute(
|
|
872
|
+
"SELECT path FROM symbols WHERE canonical_id=? OR qualified_name=? OR name=? LIMIT 1",
|
|
873
|
+
(s, s, s),
|
|
874
|
+
).fetchone()
|
|
875
|
+
if s_row:
|
|
876
|
+
pkg = ws_info.get_package_for_file(str(s_row[0]))
|
|
877
|
+
if pkg:
|
|
878
|
+
target_pkg = pkg
|
|
879
|
+
break
|
|
880
|
+
|
|
881
|
+
# Compute bounded package distance map (0=target, 1=direct dep/dependent, ..d=transitive, 99=unrelated)
|
|
882
|
+
pkg_distances: dict[str, int] = {}
|
|
883
|
+
if target_pkg:
|
|
884
|
+
pkg_distances[target_pkg.package_id] = 0
|
|
885
|
+
frontier = [target_pkg.package_id]
|
|
886
|
+
for dist in range(1, max(1, effective_pkg_depth) + 1):
|
|
887
|
+
next_frontier: list[str] = []
|
|
888
|
+
for curr_pid in frontier:
|
|
889
|
+
curr_pkg = ws_info.packages.get(curr_pid)
|
|
890
|
+
if not curr_pkg:
|
|
891
|
+
continue
|
|
892
|
+
for dep_pid in curr_pkg.dependency_package_ids:
|
|
893
|
+
if dep_pid in ws_info.packages and dep_pid not in pkg_distances:
|
|
894
|
+
pkg_distances[dep_pid] = dist
|
|
895
|
+
next_frontier.append(dep_pid)
|
|
896
|
+
for dep_pkg in ws_info.get_dependent_packages(curr_pkg.package_id):
|
|
897
|
+
if dep_pkg.package_id not in pkg_distances:
|
|
898
|
+
pkg_distances[dep_pkg.package_id] = dist
|
|
899
|
+
next_frontier.append(dep_pkg.package_id)
|
|
900
|
+
frontier = next_frontier
|
|
901
|
+
for pid in ws_info.packages:
|
|
902
|
+
if pid not in pkg_distances:
|
|
903
|
+
pkg_distances[pid] = 99
|
|
904
|
+
|
|
905
|
+
# Artifact-aware seed policy:
|
|
906
|
+
# SOURCE: full participation
|
|
907
|
+
# TEST: participation for test-aware tasks
|
|
908
|
+
# GENERATED: deprioritized (placed after handwritten source)
|
|
909
|
+
# BUILD_ARTIFACT, MINIFIED, BUNDLE, BINARY: suppressed from normal seeds
|
|
910
|
+
# VENDOR: suppressed from normal seeds unless explicit opt-in
|
|
911
|
+
filtered_seeds: list[str] = []
|
|
912
|
+
deprioritized_seeds: list[str] = []
|
|
913
|
+
excluded_artifacts_count = 0
|
|
914
|
+
excluded_artifact_classes: set[str] = set()
|
|
915
|
+
artifact_inclusions: list[dict[str, object]] = []
|
|
916
|
+
|
|
917
|
+
explicit_targets = set(task_spec.targets) | set(task_spec.priority_targets or ())
|
|
918
|
+
|
|
919
|
+
for s_candidate in ordered_seeds:
|
|
920
|
+
if s_candidate in explicit_targets:
|
|
921
|
+
filtered_seeds.append(s_candidate)
|
|
922
|
+
continue
|
|
923
|
+
cat_row = con.execute(
|
|
924
|
+
"SELECT f.category FROM symbols s JOIN files f ON s.path = f.path "
|
|
925
|
+
"WHERE s.canonical_id = ? OR s.qualified_name = ? OR s.name = ? LIMIT 1",
|
|
926
|
+
(s_candidate, s_candidate, s_candidate),
|
|
927
|
+
).fetchone()
|
|
928
|
+
if cat_row:
|
|
929
|
+
s_cat = str(cat_row[0])
|
|
930
|
+
if s_cat in ("MINIFIED", "BUNDLE") and not include_bundles:
|
|
931
|
+
excluded_artifacts_count += 1
|
|
932
|
+
excluded_artifact_classes.add(s_cat)
|
|
933
|
+
continue
|
|
934
|
+
if s_cat in ("BUILD_ARTIFACT", "BINARY") and not include_build_artifacts:
|
|
935
|
+
excluded_artifacts_count += 1
|
|
936
|
+
excluded_artifact_classes.add(s_cat)
|
|
937
|
+
continue
|
|
938
|
+
if s_cat == "VENDOR" and not include_vendor:
|
|
939
|
+
excluded_artifacts_count += 1
|
|
940
|
+
excluded_artifact_classes.add(s_cat)
|
|
941
|
+
continue
|
|
942
|
+
if s_cat == "TEST" and task_spec.intent not in ("TEST", "DEBUG", "REVIEW"):
|
|
943
|
+
continue
|
|
944
|
+
if s_cat == "GENERATED" and not include_generated:
|
|
945
|
+
deprioritized_seeds.append(s_candidate)
|
|
946
|
+
continue
|
|
947
|
+
filtered_seeds.append(s_candidate)
|
|
948
|
+
|
|
949
|
+
active_seeds = filtered_seeds + deprioritized_seeds
|
|
950
|
+
findings_out: list[dict[str, object]] = []
|
|
951
|
+
|
|
952
|
+
for sym in active_seeds[:graph_seed_policy.max_seeds]:
|
|
678
953
|
short = sym.split(".")[-1]
|
|
679
954
|
if _retrieval_policy.include_callers and (
|
|
680
955
|
retrieval_plan.entry_point_strategy in ("TRACE_FROM_ENDPOINT", "TRACE_HIERARCHY")
|
|
681
|
-
or task_spec.intent in ("DEBUG", "TRACE", "IMPACT", "CHANGE", "REVIEW")
|
|
956
|
+
or task_spec.intent in ("DEBUG", "TRACE", "IMPACT", "CHANGE", "REVIEW", "UNDERSTAND")
|
|
682
957
|
):
|
|
683
958
|
callers = find_callers(con, short, max_results=max_graph_results)
|
|
684
959
|
for c in callers:
|
|
@@ -725,7 +1000,7 @@ def _get_context_impl(
|
|
|
725
1000
|
}
|
|
726
1001
|
)
|
|
727
1002
|
# Depth 2 callee and parallel discovery
|
|
728
|
-
if
|
|
1003
|
+
if effective_max_depth >= 2 and callee_sym:
|
|
729
1004
|
c2_list = find_callees(con, str(callee_sym), max_results=max_graph_results)
|
|
730
1005
|
for c2 in c2_list:
|
|
731
1006
|
c2_sym = c2.get("callee")
|
|
@@ -789,6 +1064,62 @@ def _get_context_impl(
|
|
|
789
1064
|
}
|
|
790
1065
|
)
|
|
791
1066
|
|
|
1067
|
+
# Phase 10: Semantic edge candidate discovery (DI providers, registrations, events, routes, tests)
|
|
1068
|
+
# Ensures coverage-aware optimization discovers high-value semantic neighbors even when lexically distinct
|
|
1069
|
+
try:
|
|
1070
|
+
sem_rows = con.execute(
|
|
1071
|
+
"""
|
|
1072
|
+
SELECT source, target, relationship, confidence, evidence_class, file, start_line, evidence
|
|
1073
|
+
FROM graph_edges
|
|
1074
|
+
WHERE source = ? OR target = ? OR source LIKE ? OR target LIKE ?
|
|
1075
|
+
LIMIT 30
|
|
1076
|
+
""",
|
|
1077
|
+
(sym, sym, f"%::{short}", f"%::{short}"),
|
|
1078
|
+
).fetchall()
|
|
1079
|
+
for srow in sem_rows:
|
|
1080
|
+
s_rel = str(srow["relationship"] or "")
|
|
1081
|
+
s_conf = str(srow["confidence"] or "HIGH").upper()
|
|
1082
|
+
s_ev_cls = str(srow["evidence_class"] or "AST_VERIFIED").upper()
|
|
1083
|
+
if s_ev_cls in ("UNKNOWN", "POSSIBLE") or s_conf == "LOW":
|
|
1084
|
+
findings_out.append({
|
|
1085
|
+
"type": "EPISTEMIC_EDGE",
|
|
1086
|
+
"source": srow["source"],
|
|
1087
|
+
"target": srow["target"],
|
|
1088
|
+
"relationship": s_rel,
|
|
1089
|
+
"evidence_class": s_ev_cls,
|
|
1090
|
+
"confidence": s_conf,
|
|
1091
|
+
"file": srow["file"],
|
|
1092
|
+
"line": srow["start_line"],
|
|
1093
|
+
"evidence": srow["evidence"] or "",
|
|
1094
|
+
"status": "UNKNOWN" if s_ev_cls == "UNKNOWN" else "INFERENCE",
|
|
1095
|
+
})
|
|
1096
|
+
if not _retrieval_policy.allows(s_rel):
|
|
1097
|
+
continue
|
|
1098
|
+
other_id = str(srow["target"]) if str(srow["source"]).endswith(short) or str(srow["source"]) == sym else str(srow["source"])
|
|
1099
|
+
target_symbols.add(other_id)
|
|
1100
|
+
sym_lookup = con.execute(
|
|
1101
|
+
"SELECT canonical_id, qualified_name, name, path, start_line, end_line FROM symbols "
|
|
1102
|
+
"WHERE canonical_id = ? OR qualified_name = ? OR name = ? LIMIT 1",
|
|
1103
|
+
(other_id, other_id, other_id.split("::")[-1].split(".")[-1]),
|
|
1104
|
+
).fetchone()
|
|
1105
|
+
if sym_lookup:
|
|
1106
|
+
o_file = str(sym_lookup["path"])
|
|
1107
|
+
o_sym = str(sym_lookup["qualified_name"] or sym_lookup["name"])
|
|
1108
|
+
if _guard.allows_symbol(o_sym) and _guard.allows_file(o_file):
|
|
1109
|
+
candidate_dicts.append({
|
|
1110
|
+
"file": o_file,
|
|
1111
|
+
"symbol": o_sym,
|
|
1112
|
+
"canonical_id": str(sym_lookup["canonical_id"]),
|
|
1113
|
+
"start_line": int(sym_lookup["start_line"] or 1),
|
|
1114
|
+
"end_line": int(sym_lookup["end_line"] or 1),
|
|
1115
|
+
"score": 0.65,
|
|
1116
|
+
"snippet": "",
|
|
1117
|
+
"relationship": s_rel,
|
|
1118
|
+
"confidence": s_conf,
|
|
1119
|
+
})
|
|
1120
|
+
except Exception:
|
|
1121
|
+
pass
|
|
1122
|
+
|
|
792
1123
|
# Also discover parallel implementations for top search results
|
|
793
1124
|
if _retrieval_policy.allows("PARALLEL_IMPLEMENTATION"):
|
|
794
1125
|
for s_item in search_res[:4]:
|
|
@@ -819,7 +1150,8 @@ def _get_context_impl(
|
|
|
819
1150
|
# Related tests
|
|
820
1151
|
t_tests_start = time.perf_counter()
|
|
821
1152
|
tests_out: list[TestRef] = []
|
|
822
|
-
|
|
1153
|
+
seen_test_keys: set[tuple[str, str | None, str]] = set()
|
|
1154
|
+
if retrieval_plan.include_tests or _retrieval_policy.include_tests:
|
|
823
1155
|
test_sym_targets = list(task_spec.priority_targets or task_spec.targets)
|
|
824
1156
|
if not test_sym_targets:
|
|
825
1157
|
test_sym_targets = list(target_symbols)
|
|
@@ -828,27 +1160,91 @@ def _get_context_impl(
|
|
|
828
1160
|
for test_item in test_results:
|
|
829
1161
|
if "result" in test_item:
|
|
830
1162
|
continue
|
|
1163
|
+
t_file = str(test_item.get("file", ""))
|
|
1164
|
+
t_sym = str(test_item["symbol"]) if test_item.get("symbol") is not None else None
|
|
1165
|
+
t_rel = str(test_item.get("relationship", "TESTS"))
|
|
1166
|
+
t_key = (t_file, t_sym, t_rel)
|
|
1167
|
+
if t_key in seen_test_keys:
|
|
1168
|
+
continue
|
|
1169
|
+
seen_test_keys.add(t_key)
|
|
831
1170
|
candidate_dicts.append(
|
|
832
1171
|
{
|
|
833
|
-
"file":
|
|
834
|
-
"symbol":
|
|
1172
|
+
"file": t_file,
|
|
1173
|
+
"symbol": t_sym,
|
|
835
1174
|
"start_line": test_item.get("start_line", 1),
|
|
836
1175
|
"end_line": test_item.get("start_line", 1),
|
|
837
|
-
"score": 0.
|
|
1176
|
+
"score": 0.60,
|
|
838
1177
|
"snippet": "",
|
|
1178
|
+
"relationship": t_rel,
|
|
839
1179
|
}
|
|
840
1180
|
)
|
|
841
1181
|
tests_out.append(
|
|
842
1182
|
TestRef(
|
|
843
|
-
file=
|
|
844
|
-
symbol=
|
|
845
|
-
relationship=
|
|
1183
|
+
file=t_file,
|
|
1184
|
+
symbol=t_sym,
|
|
1185
|
+
relationship=t_rel,
|
|
846
1186
|
confidence=str(test_item.get("confidence", "LOW")),
|
|
847
1187
|
evidence=str(test_item.get("evidence", "")),
|
|
848
1188
|
)
|
|
849
1189
|
)
|
|
850
1190
|
timing.tests_ms = (time.perf_counter() - t_tests_start) * 1000
|
|
851
1191
|
|
|
1192
|
+
# Pre-ranking artifact & package annotation on candidate_dicts
|
|
1193
|
+
from codegraph.indexing.classifier import classify_file
|
|
1194
|
+
from codegraph.security import is_sensitive_path
|
|
1195
|
+
filtered_candidate_dicts: list[dict[str, object]] = []
|
|
1196
|
+
for cd in candidate_dicts:
|
|
1197
|
+
c_file = str(cd.get("file") or "")
|
|
1198
|
+
if c_file and is_sensitive_path(c_file):
|
|
1199
|
+
continue
|
|
1200
|
+
c_sym = str(cd.get("symbol") or "")
|
|
1201
|
+
c_cat = str(cd.get("category") or "")
|
|
1202
|
+
if not c_cat and c_file:
|
|
1203
|
+
frow = con.execute("SELECT category FROM files WHERE path=? LIMIT 1", (c_file,)).fetchone()
|
|
1204
|
+
c_cat = str(frow[0]) if frow and frow[0] else str(classify_file(c_file).value)
|
|
1205
|
+
cd["category"] = c_cat
|
|
1206
|
+
|
|
1207
|
+
is_explicit = c_file in explicit_targets or c_sym in explicit_targets
|
|
1208
|
+
if not is_explicit:
|
|
1209
|
+
if c_cat in ("MINIFIED", "BUNDLE") and not include_bundles:
|
|
1210
|
+
excluded_artifacts_count += 1
|
|
1211
|
+
excluded_artifact_classes.add(c_cat)
|
|
1212
|
+
continue
|
|
1213
|
+
if c_cat in ("BUILD_ARTIFACT", "BINARY") and not include_build_artifacts:
|
|
1214
|
+
excluded_artifacts_count += 1
|
|
1215
|
+
excluded_artifact_classes.add(c_cat)
|
|
1216
|
+
continue
|
|
1217
|
+
if c_cat == "VENDOR" and not include_vendor:
|
|
1218
|
+
excluded_artifacts_count += 1
|
|
1219
|
+
excluded_artifact_classes.add(c_cat)
|
|
1220
|
+
continue
|
|
1221
|
+
else:
|
|
1222
|
+
if c_cat in ("MINIFIED", "BUNDLE", "BUILD_ARTIFACT", "VENDOR", "GENERATED"):
|
|
1223
|
+
artifact_inclusions.append({
|
|
1224
|
+
"file": c_file,
|
|
1225
|
+
"artifact_class": c_cat,
|
|
1226
|
+
"reason": "Explicitly targeted by task",
|
|
1227
|
+
})
|
|
1228
|
+
|
|
1229
|
+
if c_cat in ("MINIFIED", "BUNDLE") and include_bundles:
|
|
1230
|
+
artifact_inclusions.append({"file": c_file, "artifact_class": c_cat, "reason": "Explicit opt-in: include_bundles=True"})
|
|
1231
|
+
elif c_cat in ("BUILD_ARTIFACT", "BINARY") and include_build_artifacts:
|
|
1232
|
+
artifact_inclusions.append({"file": c_file, "artifact_class": c_cat, "reason": "Explicit opt-in: include_build_artifacts=True"})
|
|
1233
|
+
elif c_cat == "VENDOR" and include_vendor:
|
|
1234
|
+
artifact_inclusions.append({"file": c_file, "artifact_class": c_cat, "reason": "Explicit opt-in: include_vendor=True"})
|
|
1235
|
+
elif c_cat == "GENERATED" and include_generated:
|
|
1236
|
+
artifact_inclusions.append({"file": c_file, "artifact_class": c_cat, "reason": "Explicit opt-in: include_generated=True"})
|
|
1237
|
+
|
|
1238
|
+
if target_pkg and c_file:
|
|
1239
|
+
c_pkg = ws_info.get_package_for_file(c_file)
|
|
1240
|
+
if c_pkg:
|
|
1241
|
+
cd["package_id"] = c_pkg.package_id
|
|
1242
|
+
cd["package_distance"] = pkg_distances.get(c_pkg.package_id, 99)
|
|
1243
|
+
|
|
1244
|
+
filtered_candidate_dicts.append(cd)
|
|
1245
|
+
|
|
1246
|
+
candidate_dicts = filtered_candidate_dicts
|
|
1247
|
+
|
|
852
1248
|
# 6. Rank candidates
|
|
853
1249
|
t_rank_start = time.perf_counter()
|
|
854
1250
|
legacy_intent_str = str(caller_raw_intent or task_spec.intent.lower())
|
|
@@ -863,20 +1259,40 @@ def _get_context_impl(
|
|
|
863
1259
|
)
|
|
864
1260
|
timing.ranking_ms = (time.perf_counter() - t_rank_start) * 1000
|
|
865
1261
|
|
|
866
|
-
# 7. Snippets and Evidence
|
|
1262
|
+
# 7. Bounded Snippets and Evidence
|
|
1263
|
+
from codegraph.security import redact_secrets
|
|
867
1264
|
t_evidence_start = time.perf_counter()
|
|
1265
|
+
per_snippet_max_lines = max(1, min(35, min(max_lines, HARD_MAX_LINES)))
|
|
868
1266
|
for item in ranked:
|
|
869
1267
|
if not item.snippet and item.symbol:
|
|
870
1268
|
row = con.execute(
|
|
871
1269
|
"SELECT content FROM chunks WHERE symbol=? LIMIT 1", (item.symbol,)
|
|
872
1270
|
).fetchone()
|
|
873
1271
|
if row:
|
|
874
|
-
snip =
|
|
1272
|
+
snip, _, _ = select_bounded_snippet(
|
|
1273
|
+
redact_secrets(str(row["content"])),
|
|
1274
|
+
start_line=item.start_line,
|
|
1275
|
+
end_line=item.end_line,
|
|
1276
|
+
max_lines=per_snippet_max_lines,
|
|
1277
|
+
max_chars=800,
|
|
1278
|
+
)
|
|
1279
|
+
snip = redact_secrets(snip)
|
|
875
1280
|
object.__setattr__(item, "snippet", snip)
|
|
876
1281
|
object.__setattr__(item, "token_estimate", _token_estimate(snip))
|
|
1282
|
+
elif item.snippet:
|
|
1283
|
+
snip, _, _ = select_bounded_snippet(
|
|
1284
|
+
redact_secrets(item.snippet),
|
|
1285
|
+
start_line=item.start_line,
|
|
1286
|
+
end_line=item.end_line,
|
|
1287
|
+
max_lines=per_snippet_max_lines,
|
|
1288
|
+
max_chars=800,
|
|
1289
|
+
)
|
|
1290
|
+
snip = redact_secrets(snip)
|
|
1291
|
+
object.__setattr__(item, "snippet", snip)
|
|
1292
|
+
object.__setattr__(item, "token_estimate", _token_estimate(snip))
|
|
877
1293
|
timing.evidence_ms = (time.perf_counter() - t_evidence_start) * 1000
|
|
878
1294
|
|
|
879
|
-
# 8. Token Budget Optimization
|
|
1295
|
+
# 8. Token Budget & Coverage-Aware Optimization
|
|
880
1296
|
t_compile_start = time.perf_counter()
|
|
881
1297
|
candidate_items: list[CandidateContextItem] = []
|
|
882
1298
|
for idx, item in enumerate(ranked):
|
|
@@ -892,15 +1308,45 @@ def _get_context_impl(
|
|
|
892
1308
|
elif "model" in item.file.lower() or "db" in item.file.lower() or "schema" in item.file.lower():
|
|
893
1309
|
layer = "DATA"
|
|
894
1310
|
|
|
895
|
-
canon =
|
|
896
|
-
if item.symbol:
|
|
1311
|
+
canon = item.canonical_id
|
|
1312
|
+
if not canon and item.symbol:
|
|
897
1313
|
srow = con.execute(
|
|
898
|
-
"SELECT canonical_id FROM symbols WHERE canonical_id=? OR qualified_name=? OR name=? LIMIT 1",
|
|
899
|
-
(item.symbol, item.symbol, item.symbol),
|
|
1314
|
+
"SELECT canonical_id FROM symbols WHERE (canonical_id=? OR qualified_name=? OR name=?) AND path=? LIMIT 1",
|
|
1315
|
+
(item.symbol, item.symbol, item.symbol, item.file),
|
|
900
1316
|
).fetchone()
|
|
1317
|
+
if not srow:
|
|
1318
|
+
srow = con.execute(
|
|
1319
|
+
"SELECT canonical_id FROM symbols WHERE canonical_id=? OR qualified_name=? OR name=? LIMIT 1",
|
|
1320
|
+
(item.symbol, item.symbol, item.symbol),
|
|
1321
|
+
).fetchone()
|
|
901
1322
|
if srow:
|
|
902
1323
|
canon = srow["canonical_id"]
|
|
903
1324
|
|
|
1325
|
+
item_pkg = ws_info.get_package_for_file(item.file) if item.file else None
|
|
1326
|
+
item_pkg_id = item_pkg.package_id if item_pkg else None
|
|
1327
|
+
item_pkg_dist = pkg_distances.get(item_pkg_id, 0) if (target_pkg and item_pkg_id) else 0
|
|
1328
|
+
|
|
1329
|
+
frow = con.execute("SELECT category FROM files WHERE path=? LIMIT 1", (item.file,)).fetchone() if item.file else None
|
|
1330
|
+
item_art_cls = str(frow[0]) if frow and frow[0] else str(classify_file(item.file).value)
|
|
1331
|
+
|
|
1332
|
+
# Determine relationship tag from reasons if present
|
|
1333
|
+
rel_tag = ""
|
|
1334
|
+
for rs in item.reasons:
|
|
1335
|
+
if rs.code == "DI_PROVIDER":
|
|
1336
|
+
rel_tag = "PROVIDES"
|
|
1337
|
+
elif rs.code == "SEMANTIC_DISPATCH":
|
|
1338
|
+
rel_tag = "REGISTERS"
|
|
1339
|
+
elif rs.code == "ENDPOINT_HANDLER":
|
|
1340
|
+
rel_tag = "HANDLED_BY"
|
|
1341
|
+
elif rs.code in ("DIRECT_CALLER", "POSSIBLE_CALLER"):
|
|
1342
|
+
rel_tag = "CALLED_BY"
|
|
1343
|
+
elif rs.code == "DIRECT_CALLEE":
|
|
1344
|
+
rel_tag = "CALLS"
|
|
1345
|
+
elif rs.code == "TEST_RELATION":
|
|
1346
|
+
rel_tag = "TESTS"
|
|
1347
|
+
elif rs.code == "PACKAGE_DEPENDENCY":
|
|
1348
|
+
rel_tag = "DEPENDS_ON_PACKAGE"
|
|
1349
|
+
|
|
904
1350
|
candidate_items.append(
|
|
905
1351
|
CandidateContextItem(
|
|
906
1352
|
item_id=f"c_{idx}_{item.file}_{item.start_line}",
|
|
@@ -916,33 +1362,45 @@ def _get_context_impl(
|
|
|
916
1362
|
source_type="symbol" if item.symbol else "chunk",
|
|
917
1363
|
snippet=item.snippet,
|
|
918
1364
|
confidence="HIGH" if item.file not in stale_paths else "LOW",
|
|
919
|
-
relationship_value=0.8 if layer in ("ENTRYPOINT", "SERVICE", "TEST") else 0.4,
|
|
1365
|
+
relationship_value=0.8 if layer in ("ENTRYPOINT", "SERVICE", "TEST") or rel_tag else 0.4,
|
|
920
1366
|
epistemic_status="FACT" if item.file not in stale_paths else "UNKNOWN",
|
|
921
|
-
data={"reasons": item.reasons, "symbol": item.symbol},
|
|
1367
|
+
data={"reasons": item.reasons, "symbol": item.symbol, "relationship": rel_tag},
|
|
1368
|
+
package_id=item_pkg_id,
|
|
1369
|
+
package_distance=item_pkg_dist,
|
|
1370
|
+
artifact_class=item_art_cls,
|
|
922
1371
|
)
|
|
923
1372
|
)
|
|
924
1373
|
|
|
1374
|
+
budget_spec = ContextBudgetSpec(
|
|
1375
|
+
max_tokens=max_tokens,
|
|
1376
|
+
max_symbols=max_symbols,
|
|
1377
|
+
max_relationships=max_relationships,
|
|
1378
|
+
max_files=max_files,
|
|
1379
|
+
max_lines=max_lines,
|
|
1380
|
+
max_depth=effective_max_depth,
|
|
1381
|
+
)
|
|
1382
|
+
|
|
925
1383
|
selected_items, budget = optimize_context_budget(
|
|
926
1384
|
candidates=candidate_items,
|
|
927
1385
|
token_budget=max_tokens,
|
|
928
1386
|
task_spec=task_spec,
|
|
1387
|
+
budget_spec=budget_spec,
|
|
1388
|
+
required_dimensions=_retrieval_policy.required_dimensions,
|
|
1389
|
+
verified_claims_count=len(route_relationships) + len(tests_out),
|
|
1390
|
+
tool_calls=1,
|
|
929
1391
|
)
|
|
930
1392
|
timing.compilation_ms = (time.perf_counter() - t_compile_start) * 1000
|
|
931
1393
|
|
|
932
|
-
# 9. Assembly &
|
|
1394
|
+
# 9. Assembly, Deduplication & Evidence Compression
|
|
933
1395
|
t_serial_start = time.perf_counter()
|
|
934
|
-
selected_paths = {item.file_path for item in selected_items}
|
|
935
|
-
discarded_paths = {item.file_path for item in candidate_items if item.file_path not in selected_paths}
|
|
1396
|
+
selected_paths = {item.file_path for item in selected_items if item.file_path}
|
|
1397
|
+
discarded_paths = {item.file_path for item in candidate_items if item.file_path and item.file_path not in selected_paths}
|
|
936
1398
|
|
|
937
1399
|
symbols_out: list[SymbolRef] = []
|
|
938
|
-
|
|
1400
|
+
seen_canonical_syms: set[str] = set()
|
|
939
1401
|
for it in selected_items:
|
|
940
1402
|
sym_name = it.data.get("symbol")
|
|
941
1403
|
if sym_name:
|
|
942
|
-
sym_key = (sym_name, it.file_path)
|
|
943
|
-
if sym_key in seen_sym_keys:
|
|
944
|
-
continue
|
|
945
|
-
seen_sym_keys.add(sym_key)
|
|
946
1404
|
known_cid = it.canonical_id or it.data.get("canonical_id")
|
|
947
1405
|
if known_cid:
|
|
948
1406
|
row = con.execute(
|
|
@@ -960,6 +1418,10 @@ def _get_context_impl(
|
|
|
960
1418
|
(sym_name, sym_name),
|
|
961
1419
|
).fetchone()
|
|
962
1420
|
cid = known_cid or (row["canonical_id"] if row and "canonical_id" in row.keys() else None)
|
|
1421
|
+
dedup_id = str(cid) if cid else f"{it.file_path}::{sym_name}"
|
|
1422
|
+
if dedup_id in seen_canonical_syms:
|
|
1423
|
+
continue
|
|
1424
|
+
seen_canonical_syms.add(dedup_id)
|
|
963
1425
|
kind = row["kind"] if row else "unknown"
|
|
964
1426
|
reasons = it.data.get("reasons", [])
|
|
965
1427
|
symbols_out.append(
|
|
@@ -975,44 +1437,55 @@ def _get_context_impl(
|
|
|
975
1437
|
for rs in reasons
|
|
976
1438
|
],
|
|
977
1439
|
canonical_id=cid or it.canonical_id,
|
|
1440
|
+
reason_code=it.reason_code,
|
|
1441
|
+
package_id=it.package_id,
|
|
978
1442
|
)
|
|
979
1443
|
)
|
|
980
1444
|
|
|
981
|
-
# ── Relationships extraction
|
|
1445
|
+
# ── Relationships extraction & compression ──────────────────────────
|
|
982
1446
|
relationships_raw: list[Relationship] = list(route_relationships)
|
|
983
|
-
seen_rel: set[tuple[str, str, str]] = {
|
|
984
|
-
(r.source, r.target, r.relationship) for r in route_relationships
|
|
985
|
-
}
|
|
986
1447
|
|
|
987
1448
|
try:
|
|
988
|
-
all_syms =
|
|
1449
|
+
all_syms = (
|
|
1450
|
+
list(target_symbols)
|
|
1451
|
+
+ [s.split(".")[-1] for s in target_symbols]
|
|
1452
|
+
+ [s.canonical_id for s in symbols_out if s.canonical_id]
|
|
1453
|
+
+ [s.symbol for s in symbols_out if s.symbol]
|
|
1454
|
+
)
|
|
1455
|
+
if target_pkg:
|
|
1456
|
+
all_syms.append(target_pkg.package_id)
|
|
1457
|
+
all_syms.append(target_pkg.name)
|
|
1458
|
+
all_syms = list(dict.fromkeys(all_syms))
|
|
989
1459
|
if all_syms:
|
|
990
1460
|
placeholders = ",".join("?" for _ in all_syms)
|
|
991
1461
|
edge_rows = con.execute(
|
|
992
1462
|
f"""
|
|
993
|
-
SELECT source, target, relationship, confidence, file, start_line, evidence
|
|
1463
|
+
SELECT source, target, relationship, confidence, evidence_class, file, start_line, evidence
|
|
994
1464
|
FROM graph_edges
|
|
995
1465
|
WHERE source IN ({placeholders}) OR target IN ({placeholders})
|
|
996
|
-
LIMIT
|
|
1466
|
+
LIMIT 100
|
|
997
1467
|
""",
|
|
998
1468
|
all_syms + all_syms,
|
|
999
1469
|
).fetchall()
|
|
1000
1470
|
for er in edge_rows:
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1471
|
+
ev_cls = str(er["evidence_class"] if "evidence_class" in er.keys() and er["evidence_class"] else "AST_VERIFIED")
|
|
1472
|
+
conf_val = str(er["confidence"] or "HIGH").upper()
|
|
1473
|
+
status_val = "FACT" if conf_val == "HIGH" and ev_cls not in ("POSSIBLE", "UNKNOWN") else (
|
|
1474
|
+
"UNKNOWN" if ev_cls == "UNKNOWN" else "INFERENCE"
|
|
1475
|
+
)
|
|
1476
|
+
relationships_raw.append(
|
|
1477
|
+
Relationship(
|
|
1478
|
+
source=er["source"],
|
|
1479
|
+
target=er["target"],
|
|
1480
|
+
relationship=er["relationship"],
|
|
1481
|
+
confidence=conf_val,
|
|
1482
|
+
file=er["file"],
|
|
1483
|
+
start_line=er["start_line"],
|
|
1484
|
+
evidence=er["evidence"] or "",
|
|
1485
|
+
status=status_val,
|
|
1486
|
+
evidence_class=ev_cls,
|
|
1015
1487
|
)
|
|
1488
|
+
)
|
|
1016
1489
|
except Exception:
|
|
1017
1490
|
pass
|
|
1018
1491
|
|
|
@@ -1023,63 +1496,79 @@ def _get_context_impl(
|
|
|
1023
1496
|
for c in callers:
|
|
1024
1497
|
c_file = str(c.get("file", ""))
|
|
1025
1498
|
if c_file in selected_paths:
|
|
1026
|
-
|
|
1027
|
-
if
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
)
|
|
1499
|
+
raw_c_conf = str(c.get("confidence", "LOW")).upper()
|
|
1500
|
+
c_conf = "MEDIUM" if raw_c_conf == "HIGH" else raw_c_conf
|
|
1501
|
+
relationships_raw.append(
|
|
1502
|
+
Relationship(
|
|
1503
|
+
source=c_file,
|
|
1504
|
+
target=sym,
|
|
1505
|
+
relationship="POSSIBLE_CALLS",
|
|
1506
|
+
confidence=c_conf,
|
|
1507
|
+
file=c_file,
|
|
1508
|
+
evidence=str(c.get("evidence", "")),
|
|
1509
|
+
status="INFERENCE",
|
|
1510
|
+
evidence_class="POSSIBLE",
|
|
1039
1511
|
)
|
|
1512
|
+
)
|
|
1040
1513
|
|
|
1041
|
-
# ── v2.1 HARD SAFETY & INTEGRITY INVARIANT
|
|
1042
|
-
# Every channel must strictly conform to ConstraintGuard and RetrievalPolicy:
|
|
1514
|
+
# ── v2.1 & Phase 10 HARD SAFETY & INTEGRITY INVARIANT ───────────────
|
|
1043
1515
|
# 1. Symbols
|
|
1044
1516
|
symbols_out = [
|
|
1045
1517
|
s for s in symbols_out
|
|
1046
1518
|
if _guard.allows_symbol(s.symbol) and _guard.allows_canonical_id(s.canonical_id) and _guard.allows_file(s.file)
|
|
1047
|
-
]
|
|
1519
|
+
][: budget.max_symbols]
|
|
1048
1520
|
|
|
1049
|
-
# 2. Relationships
|
|
1050
|
-
|
|
1521
|
+
# 2. Relationships (filtered + deterministically compressed)
|
|
1522
|
+
filtered_rels: list[Relationship] = [
|
|
1051
1523
|
r for r in relationships_raw
|
|
1052
1524
|
if _guard.allows_relationship(r.source, r.relationship, r.target)
|
|
1053
1525
|
and (_guard.allows_file(r.file) if r.file else True)
|
|
1054
1526
|
and _retrieval_policy.allows(r.relationship)
|
|
1055
1527
|
]
|
|
1528
|
+
relationships_out, compression_stats = compress_relationships(
|
|
1529
|
+
filtered_rels, max_relationships=budget.max_relationships
|
|
1530
|
+
)
|
|
1056
1531
|
|
|
1057
|
-
# 3. Tests
|
|
1532
|
+
# 3. Tests (deduplicated)
|
|
1058
1533
|
tests_out = [
|
|
1059
1534
|
t for t in tests_out
|
|
1060
1535
|
if _guard.allows_test(t.file, t.symbol)
|
|
1061
1536
|
]
|
|
1062
1537
|
|
|
1063
|
-
# 4. Entry points & framework facts
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1538
|
+
# 4. Entry points & framework facts (deduplicated by endpoint_id)
|
|
1539
|
+
seen_ep_ids: set[str] = set()
|
|
1540
|
+
dedup_entry_points: list[dict[str, object]] = []
|
|
1541
|
+
for ep in entry_points_out:
|
|
1542
|
+
if not _guard.allows_endpoint(
|
|
1067
1543
|
str(ep.get("route_path")),
|
|
1068
1544
|
str(ep.get("handler")),
|
|
1069
1545
|
str(ep.get("file")),
|
|
1070
|
-
)
|
|
1071
|
-
|
|
1546
|
+
):
|
|
1547
|
+
continue
|
|
1548
|
+
eid = str(ep.get("endpoint_id") or "")
|
|
1549
|
+
if eid and eid in seen_ep_ids:
|
|
1550
|
+
continue
|
|
1551
|
+
if eid:
|
|
1552
|
+
seen_ep_ids.add(eid)
|
|
1553
|
+
dedup_entry_points.append(ep)
|
|
1554
|
+
entry_points_out = dedup_entry_points
|
|
1555
|
+
|
|
1072
1556
|
framework_facts_out = [
|
|
1073
1557
|
ff for ff in framework_facts_out
|
|
1074
1558
|
if _guard.allows_symbol(str(ff.get("handler"))) and _guard.allows_file(str(ff.get("file")))
|
|
1075
1559
|
]
|
|
1076
1560
|
|
|
1077
|
-
# 5. Evidence
|
|
1561
|
+
# 5. Evidence (bounded & deduplicated by canonical location)
|
|
1078
1562
|
evidence_out: list[EvidenceRef] = []
|
|
1563
|
+
seen_ev_keys: set[tuple[str, int, str | None]] = set()
|
|
1079
1564
|
for it in selected_items[:12]:
|
|
1080
1565
|
if it.snippet:
|
|
1081
1566
|
if not _guard.allows_file(it.file_path) or not _guard.allows_symbol(it.data.get("symbol")):
|
|
1082
1567
|
continue
|
|
1568
|
+
ev_key = (it.file_path, it.start_line, it.data.get("symbol"))
|
|
1569
|
+
if ev_key in seen_ev_keys:
|
|
1570
|
+
continue
|
|
1571
|
+
seen_ev_keys.add(ev_key)
|
|
1083
1572
|
src_hash: str | None = None
|
|
1084
1573
|
try:
|
|
1085
1574
|
row = con.execute("SELECT content_hash FROM files WHERE path=?", (it.file_path,)).fetchone()
|
|
@@ -1101,15 +1590,20 @@ def _get_context_impl(
|
|
|
1101
1590
|
)
|
|
1102
1591
|
)
|
|
1103
1592
|
|
|
1104
|
-
# 6. Files
|
|
1593
|
+
# 6. Files (bounded by budget.max_files, aggregating token_estimate across selected items in each file)
|
|
1105
1594
|
files_out: list[FileRef] = []
|
|
1106
|
-
|
|
1595
|
+
file_index_map: dict[str, int] = {}
|
|
1107
1596
|
for it in selected_items:
|
|
1108
|
-
if not _guard.allows_file(it.file_path):
|
|
1597
|
+
if not it.file_path or not _guard.allows_file(it.file_path):
|
|
1598
|
+
continue
|
|
1599
|
+
if it.file_path in file_index_map:
|
|
1600
|
+
existing_idx = file_index_map[it.file_path]
|
|
1601
|
+
existing_ref = files_out[existing_idx]
|
|
1602
|
+
existing_ref.token_estimate += it.estimated_tokens
|
|
1109
1603
|
continue
|
|
1110
|
-
if
|
|
1604
|
+
if len(files_out) >= budget.max_files:
|
|
1111
1605
|
continue
|
|
1112
|
-
|
|
1606
|
+
file_index_map[it.file_path] = len(files_out)
|
|
1113
1607
|
reasons = it.data.get("reasons", [])
|
|
1114
1608
|
files_out.append(
|
|
1115
1609
|
FileRef(
|
|
@@ -1124,6 +1618,184 @@ def _get_context_impl(
|
|
|
1124
1618
|
)
|
|
1125
1619
|
)
|
|
1126
1620
|
|
|
1621
|
+
# 7. Package-aware bounded packages list & unresolved package dependency preservation
|
|
1622
|
+
packages_out: list[dict[str, object]] = []
|
|
1623
|
+
seen_pkg_ids: set[str] = set()
|
|
1624
|
+
for pid in sorted(ws_info.packages.keys()):
|
|
1625
|
+
p_info = ws_info.packages[pid]
|
|
1626
|
+
if p_info.root_path == "" and len(ws_info.packages) > 1:
|
|
1627
|
+
continue
|
|
1628
|
+
p_dist = pkg_distances.get(pid, 0)
|
|
1629
|
+
if target_pkg and p_dist > effective_pkg_depth and task_spec.intent != "ARCHITECTURE":
|
|
1630
|
+
continue
|
|
1631
|
+
if pid not in seen_pkg_ids:
|
|
1632
|
+
seen_pkg_ids.add(pid)
|
|
1633
|
+
p_dict = p_info.as_dict()
|
|
1634
|
+
p_dict["distance"] = p_dist
|
|
1635
|
+
packages_out.append(p_dict)
|
|
1636
|
+
|
|
1637
|
+
for unres in ws_info.unresolved_dependencies:
|
|
1638
|
+
src_pid = str(unres.get("source_package_id") or "")
|
|
1639
|
+
if not target_pkg or src_pid == target_pkg.package_id:
|
|
1640
|
+
findings_out.append({
|
|
1641
|
+
"type": "UNRESOLVED_PACKAGE_DEPENDENCY",
|
|
1642
|
+
"source": src_pid,
|
|
1643
|
+
"target": str(unres.get("target_name") or ""),
|
|
1644
|
+
"specifier": str(unres.get("specifier") or ""),
|
|
1645
|
+
"status": "UNKNOWN",
|
|
1646
|
+
"reason": f"Dependency '{unres.get('target_name')}' is external or unresolved in workspace",
|
|
1647
|
+
})
|
|
1648
|
+
|
|
1649
|
+
# Check unresolved target resolutions and preserve as UNKNOWN findings
|
|
1650
|
+
assumptions_out = list(task_spec.assumptions)
|
|
1651
|
+
unknowns_out = list(task_spec.unknowns)
|
|
1652
|
+
conflicts_out = list(task_spec.conflicts) if hasattr(task_spec, "conflicts") else []
|
|
1653
|
+
|
|
1654
|
+
for raw_t, t_res in _target_resolutions.items():
|
|
1655
|
+
if t_res.ambiguity_state == "AMBIGUOUS":
|
|
1656
|
+
findings_out.append({
|
|
1657
|
+
"type": "AMBIGUOUS_TARGET",
|
|
1658
|
+
"target": raw_t,
|
|
1659
|
+
"status": "CONFLICT",
|
|
1660
|
+
"alternatives": list(t_res.alternatives),
|
|
1661
|
+
})
|
|
1662
|
+
msg = f"Target '{raw_t}' is ambiguous across multiple candidates."
|
|
1663
|
+
if msg not in conflicts_out:
|
|
1664
|
+
conflicts_out.append(msg)
|
|
1665
|
+
elif t_res.target_type.value == "UNKNOWN":
|
|
1666
|
+
findings_out.append({
|
|
1667
|
+
"type": "UNRESOLVED_TARGET",
|
|
1668
|
+
"target": raw_t,
|
|
1669
|
+
"status": "UNKNOWN",
|
|
1670
|
+
"reason": f"Target '{raw_t}' could not be statically resolved in repository.",
|
|
1671
|
+
})
|
|
1672
|
+
msg = f"Target '{raw_t}' could not be statically resolved."
|
|
1673
|
+
if msg not in unknowns_out:
|
|
1674
|
+
unknowns_out.append(msg)
|
|
1675
|
+
|
|
1676
|
+
# 8. Phase 14: Database, Runtime & Reconciliation Context Assembly
|
|
1677
|
+
database_out: list[dict[str, object]] = []
|
|
1678
|
+
runtime_out: list[dict[str, object]] = []
|
|
1679
|
+
reconciliation_out: list[dict[str, object]] = []
|
|
1680
|
+
|
|
1681
|
+
try:
|
|
1682
|
+
relevant_names: set[str] = set()
|
|
1683
|
+
for t in task_spec.targets:
|
|
1684
|
+
if t:
|
|
1685
|
+
relevant_names.add(t.lower())
|
|
1686
|
+
relevant_names.add(t.split(".")[-1].lower())
|
|
1687
|
+
for s_ref in symbols_out:
|
|
1688
|
+
relevant_names.add(s_ref.symbol.lower())
|
|
1689
|
+
relevant_names.add(s_ref.symbol.split(".")[-1].lower())
|
|
1690
|
+
if s_ref.canonical_id:
|
|
1691
|
+
relevant_names.add(s_ref.canonical_id.lower())
|
|
1692
|
+
for word in task_display_str.lower().replace(",", " ").replace("(", " ").replace(")", " ").split():
|
|
1693
|
+
if len(word) >= 3:
|
|
1694
|
+
relevant_names.add(word)
|
|
1695
|
+
|
|
1696
|
+
db_ent_rows = con.execute(
|
|
1697
|
+
"SELECT canonical_id, entity_type, name, dialect, schema_name, table_name, data_type, "
|
|
1698
|
+
"nullable, primary_key, foreign_key_target, path, start_line, end_line, confidence, evidence_class "
|
|
1699
|
+
"FROM db_entities ORDER BY table_name ASC, entity_type ASC, name ASC LIMIT 80"
|
|
1700
|
+
).fetchall()
|
|
1701
|
+
for drow in db_ent_rows:
|
|
1702
|
+
t_name = str(drow["table_name"] or drow["name"] or "").lower()
|
|
1703
|
+
e_name = str(drow["name"] or "").lower()
|
|
1704
|
+
d_path = str(drow["path"] or "")
|
|
1705
|
+
if (
|
|
1706
|
+
not _guard.allows_file(d_path)
|
|
1707
|
+
or not _guard.allows_symbol(str(drow["name"] or ""))
|
|
1708
|
+
):
|
|
1709
|
+
continue
|
|
1710
|
+
if (
|
|
1711
|
+
t_name in relevant_names
|
|
1712
|
+
or e_name in relevant_names
|
|
1713
|
+
or d_path in selected_paths
|
|
1714
|
+
or any(rn and (rn in t_name or t_name in rn) for rn in relevant_names if len(rn) >= 4)
|
|
1715
|
+
):
|
|
1716
|
+
database_out.append({
|
|
1717
|
+
"record_type": "entity",
|
|
1718
|
+
"canonical_id": str(drow["canonical_id"]),
|
|
1719
|
+
"entity_type": str(drow["entity_type"]),
|
|
1720
|
+
"name": str(drow["name"]),
|
|
1721
|
+
"dialect": str(drow["dialect"]),
|
|
1722
|
+
"schema": str(drow["schema_name"]),
|
|
1723
|
+
"table": str(drow["table_name"] or ""),
|
|
1724
|
+
"data_type": drow["data_type"],
|
|
1725
|
+
"primary_key": bool(drow["primary_key"]),
|
|
1726
|
+
"foreign_key_target": drow["foreign_key_target"],
|
|
1727
|
+
"file": d_path,
|
|
1728
|
+
"start_line": int(drow["start_line"] or 1),
|
|
1729
|
+
"end_line": int(drow["end_line"] or 1),
|
|
1730
|
+
"confidence": str(drow["confidence"]),
|
|
1731
|
+
"evidence_class": str(drow["evidence_class"]),
|
|
1732
|
+
})
|
|
1733
|
+
if len(database_out) >= 20:
|
|
1734
|
+
break
|
|
1735
|
+
|
|
1736
|
+
db_q_rows = con.execute(
|
|
1737
|
+
"SELECT source_symbol, target_table, operation, relationship, path, start_line, end_line, "
|
|
1738
|
+
"confidence, evidence_class, snippet, framework FROM db_queries "
|
|
1739
|
+
"ORDER BY path ASC, start_line ASC LIMIT 50"
|
|
1740
|
+
).fetchall()
|
|
1741
|
+
for qrow in db_q_rows:
|
|
1742
|
+
q_path = str(qrow["path"] or "")
|
|
1743
|
+
q_sym = str(qrow["source_symbol"] or "")
|
|
1744
|
+
q_tbl = str(qrow["target_table"] or "").lower()
|
|
1745
|
+
if not _guard.allows_file(q_path) or not _guard.allows_symbol(q_sym):
|
|
1746
|
+
continue
|
|
1747
|
+
if (
|
|
1748
|
+
q_path in selected_paths
|
|
1749
|
+
or q_tbl in relevant_names
|
|
1750
|
+
or q_sym.lower() in relevant_names
|
|
1751
|
+
or q_sym.split("::")[-1].split(".")[-1].lower() in relevant_names
|
|
1752
|
+
):
|
|
1753
|
+
database_out.append({
|
|
1754
|
+
"record_type": "query",
|
|
1755
|
+
"source_symbol": q_sym,
|
|
1756
|
+
"target_table": str(qrow["target_table"]),
|
|
1757
|
+
"operation": str(qrow["operation"]),
|
|
1758
|
+
"relationship": str(qrow["relationship"]),
|
|
1759
|
+
"framework": str(qrow["framework"] or ""),
|
|
1760
|
+
"file": q_path,
|
|
1761
|
+
"start_line": int(qrow["start_line"] or 1),
|
|
1762
|
+
"end_line": int(qrow["end_line"] or 1),
|
|
1763
|
+
"confidence": str(qrow["confidence"]),
|
|
1764
|
+
"evidence_class": str(qrow["evidence_class"]),
|
|
1765
|
+
"snippet": redact_secrets(str(qrow["snippet"] or "")[:240]),
|
|
1766
|
+
})
|
|
1767
|
+
if len(database_out) >= 30:
|
|
1768
|
+
break
|
|
1769
|
+
except Exception:
|
|
1770
|
+
pass
|
|
1771
|
+
|
|
1772
|
+
try:
|
|
1773
|
+
rt_rows = con.execute(
|
|
1774
|
+
"SELECT source, target, relationship, observation_count, first_seen, last_seen, "
|
|
1775
|
+
"runtime_generation, confidence, evidence_class, sample_trace_id "
|
|
1776
|
+
"FROM runtime_edges ORDER BY observation_count DESC, source ASC, target ASC LIMIT 25"
|
|
1777
|
+
).fetchall()
|
|
1778
|
+
for rrow in rt_rows:
|
|
1779
|
+
runtime_out.append({
|
|
1780
|
+
"source": str(rrow["source"]),
|
|
1781
|
+
"target": str(rrow["target"]),
|
|
1782
|
+
"relationship": str(rrow["relationship"]),
|
|
1783
|
+
"observation_count": int(rrow["observation_count"] or 1),
|
|
1784
|
+
"first_seen": str(rrow["first_seen"] or ""),
|
|
1785
|
+
"last_seen": str(rrow["last_seen"] or ""),
|
|
1786
|
+
"runtime_generation": int(rrow["runtime_generation"] or 1),
|
|
1787
|
+
"confidence": str(rrow["confidence"] or "HIGH"),
|
|
1788
|
+
"evidence_class": "RUNTIME_OBSERVED",
|
|
1789
|
+
"sample_trace_id": str(rrow["sample_trace_id"] or ""),
|
|
1790
|
+
})
|
|
1791
|
+
if runtime_out:
|
|
1792
|
+
from codegraph.runtime.reconciliation import reconcile_static_runtime
|
|
1793
|
+
rec_res = reconcile_static_runtime(con, repository)
|
|
1794
|
+
for item_rec in rec_res.get("items", [])[:20]:
|
|
1795
|
+
reconciliation_out.append(dict(item_rec))
|
|
1796
|
+
except Exception:
|
|
1797
|
+
pass
|
|
1798
|
+
|
|
1127
1799
|
uncertainties: list[str] = []
|
|
1128
1800
|
if freshness_report.status != FreshnessStatus.FRESH:
|
|
1129
1801
|
uncertainties.append(f"Index is {freshness_report.status.value}: {freshness_report.detail}")
|
|
@@ -1132,9 +1804,6 @@ def _get_context_impl(
|
|
|
1132
1804
|
if not candidate_dicts:
|
|
1133
1805
|
uncertainties.append("No lexical or graph matches found. Results may be incomplete.")
|
|
1134
1806
|
|
|
1135
|
-
assumptions_out = list(task_spec.assumptions)
|
|
1136
|
-
unknowns_out = list(task_spec.unknowns)
|
|
1137
|
-
|
|
1138
1807
|
timing.serialization_ms = (time.perf_counter() - t_serial_start) * 1000
|
|
1139
1808
|
timing.total_ms = timer.elapsed_ms
|
|
1140
1809
|
|
|
@@ -1148,6 +1817,26 @@ def _get_context_impl(
|
|
|
1148
1817
|
timing=timing,
|
|
1149
1818
|
)
|
|
1150
1819
|
exec_dict = exec_meta.as_dict()
|
|
1820
|
+
exec_dict["excluded_count"] = excluded_artifacts_count
|
|
1821
|
+
exec_dict["excluded_artifact_classes"] = sorted(excluded_artifact_classes)
|
|
1822
|
+
exec_dict["artifact_inclusions"] = artifact_inclusions
|
|
1823
|
+
exec_dict["package_context"] = target_pkg.as_dict() if target_pkg else None
|
|
1824
|
+
exec_dict["evidence_compression"] = compression_stats
|
|
1825
|
+
exec_dict["tool_efficiency"] = {
|
|
1826
|
+
"tool_calls": 1,
|
|
1827
|
+
"internal_queries_executed": len(queries_to_run),
|
|
1828
|
+
"consolidated_operations": [
|
|
1829
|
+
"target_resolution",
|
|
1830
|
+
"hybrid_search",
|
|
1831
|
+
"graph_traversal",
|
|
1832
|
+
"route_matching",
|
|
1833
|
+
"database_intelligence",
|
|
1834
|
+
"runtime_reconciliation",
|
|
1835
|
+
"test_discovery",
|
|
1836
|
+
"package_boundary_filtering",
|
|
1837
|
+
"budget_optimization",
|
|
1838
|
+
],
|
|
1839
|
+
}
|
|
1151
1840
|
exec_dict["resource"] = {
|
|
1152
1841
|
"profile": governor.policy.profile.value,
|
|
1153
1842
|
"pressure": governor.get_pressure().value,
|
|
@@ -1172,6 +1861,54 @@ def _get_context_impl(
|
|
|
1172
1861
|
"rejections": list(budget.rejections),
|
|
1173
1862
|
}
|
|
1174
1863
|
|
|
1864
|
+
coverage_dict: dict[str, object] = {
|
|
1865
|
+
"coverage_score": round(budget.coverage_score, 4),
|
|
1866
|
+
"required_dimensions": list(_retrieval_policy.required_dimensions),
|
|
1867
|
+
"covered_dimensions": list(budget.covered_dimensions),
|
|
1868
|
+
"missing_dimensions": list(budget.missing_dimensions),
|
|
1869
|
+
"reason_codes": {
|
|
1870
|
+
it.canonical_id or str(it.data.get("symbol") or it.file_path): it.reason_code
|
|
1871
|
+
for it in selected_items
|
|
1872
|
+
},
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
metadata_dict: dict[str, object] = {
|
|
1876
|
+
"schema_version": SCHEMA_VERSION,
|
|
1877
|
+
"repository": str(repository),
|
|
1878
|
+
"repository_generation": gen,
|
|
1879
|
+
"freshness": freshness_report.status.value,
|
|
1880
|
+
"mode": mode_upper,
|
|
1881
|
+
"intent": str(caller_raw_intent) if caller_raw_intent else task_spec.intent.lower(),
|
|
1882
|
+
"selected_tokens": budget.selected_tokens,
|
|
1883
|
+
"candidate_tokens": budget.candidate_tokens,
|
|
1884
|
+
"selected_files": len(files_out),
|
|
1885
|
+
"selected_lines": budget.selected_lines,
|
|
1886
|
+
"coverage_score": round(budget.coverage_score, 4),
|
|
1887
|
+
"truncated": budget.truncated,
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
target_dict: dict[str, object] = {
|
|
1891
|
+
"raw_targets": list(task_spec.targets),
|
|
1892
|
+
"resolutions": {
|
|
1893
|
+
k: {
|
|
1894
|
+
"target_type": v.target_type.value,
|
|
1895
|
+
"canonical_id": v.canonical_id,
|
|
1896
|
+
"qualified_name": v.qualified_name,
|
|
1897
|
+
"file_path": v.file_path,
|
|
1898
|
+
"confidence": v.confidence,
|
|
1899
|
+
"ambiguity_state": v.ambiguity_state,
|
|
1900
|
+
}
|
|
1901
|
+
for k, v in _target_resolutions.items()
|
|
1902
|
+
},
|
|
1903
|
+
"package": target_pkg.as_dict() if target_pkg else None,
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
git_impact_dict: dict[str, object] = {
|
|
1907
|
+
"recent_changes": git_changes_out,
|
|
1908
|
+
"modified_files_count": len(recent_paths),
|
|
1909
|
+
"affected_package": target_pkg.package_id if target_pkg else None,
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1175
1912
|
packet = ContextPacket(
|
|
1176
1913
|
schema_version=SCHEMA_VERSION,
|
|
1177
1914
|
task=task_display_str,
|
|
@@ -1194,13 +1931,28 @@ def _get_context_impl(
|
|
|
1194
1931
|
git_facts=git_changes_out,
|
|
1195
1932
|
assumptions=assumptions_out,
|
|
1196
1933
|
unknowns=unknowns_out,
|
|
1197
|
-
conflicts=
|
|
1934
|
+
conflicts=conflicts_out,
|
|
1935
|
+
metadata=metadata_dict,
|
|
1936
|
+
target=target_dict,
|
|
1937
|
+
routes=entry_points_out,
|
|
1938
|
+
packages=packages_out,
|
|
1939
|
+
git_impact=git_impact_dict,
|
|
1940
|
+
findings=findings_out,
|
|
1941
|
+
coverage=coverage_dict,
|
|
1942
|
+
database=database_out,
|
|
1943
|
+
runtime=runtime_out,
|
|
1944
|
+
reconciliation=reconciliation_out,
|
|
1198
1945
|
symbols=symbols_out,
|
|
1199
1946
|
relationships=relationships_out,
|
|
1200
1947
|
tests=tests_out,
|
|
1201
1948
|
files=files_out,
|
|
1202
1949
|
evidence=evidence_out,
|
|
1203
1950
|
uncertainties=uncertainties,
|
|
1951
|
+
candidate_tokens=budget.candidate_tokens,
|
|
1952
|
+
selected_tokens=budget.selected_tokens,
|
|
1953
|
+
selected_lines=budget.selected_lines,
|
|
1954
|
+
coverage_score=round(budget.coverage_score, 4),
|
|
1955
|
+
truncated=budget.truncated,
|
|
1204
1956
|
candidate_token_estimate=budget.candidate_tokens,
|
|
1205
1957
|
selected_token_estimate=budget.selected_tokens,
|
|
1206
1958
|
context_reduction_pct=round(budget.reduction_ratio * 100, 1),
|