codegraph-engine 3.0.0__1-py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. codegraph/__init__.py +37 -0
  2. codegraph/agent.py +26 -0
  3. codegraph/agent_brain.py +2212 -0
  4. codegraph/agent_capabilities.py +4058 -0
  5. codegraph/agent_rules.py +531 -0
  6. codegraph/api_drift.py +455 -0
  7. codegraph/architecture.py +371 -0
  8. codegraph/async_queue/__init__.py +50 -0
  9. codegraph/async_queue/detectors/__init__.py +48 -0
  10. codegraph/async_queue/detectors/celery.py +160 -0
  11. codegraph/async_queue/detectors/kafka.py +168 -0
  12. codegraph/async_queue/detectors/rabbitmq.py +116 -0
  13. codegraph/async_queue/detectors/redis.py +130 -0
  14. codegraph/async_queue/detectors/sqs.py +135 -0
  15. codegraph/async_queue/models.py +161 -0
  16. codegraph/async_queue/otel_models.py +74 -0
  17. codegraph/async_queue/otel_parser.py +337 -0
  18. codegraph/async_queue/reconciler.py +427 -0
  19. codegraph/async_queue/scanner.py +291 -0
  20. codegraph/async_queue/traversal.py +447 -0
  21. codegraph/audit.py +106 -0
  22. codegraph/binding_resolver.py +616 -0
  23. codegraph/branch_comparison.py +197 -0
  24. codegraph/cache.py +95 -0
  25. codegraph/change_impact.py +547 -0
  26. codegraph/cli.py +2436 -0
  27. codegraph/cli_output.py +205 -0
  28. codegraph/config.py +45 -0
  29. codegraph/constraints.py +238 -0
  30. codegraph/context.py +2182 -0
  31. codegraph/context_freshness.py +351 -0
  32. codegraph/database/__init__.py +72 -0
  33. codegraph/database/extractor.py +3651 -0
  34. codegraph/database/interrogation.py +1494 -0
  35. codegraph/database/models.py +310 -0
  36. codegraph/database/schema_drift.py +862 -0
  37. codegraph/epistemic.py +200 -0
  38. codegraph/errors.py +282 -0
  39. codegraph/evidence/__init__.py +15 -0
  40. codegraph/evidence/citations.py +418 -0
  41. codegraph/evidence_contract.py +486 -0
  42. codegraph/frameworks.py +960 -0
  43. codegraph/frameworks_nestjs.py +502 -0
  44. codegraph/frameworks_nextjs.py +337 -0
  45. codegraph/frameworks_react.py +457 -0
  46. codegraph/freshness.py +335 -0
  47. codegraph/frontend_html.py +109 -0
  48. codegraph/git.py +362 -0
  49. codegraph/git_state.py +545 -0
  50. codegraph/graph/__init__.py +46 -0
  51. codegraph/graph/models.py +56 -0
  52. codegraph/graph/traversal.py +1993 -0
  53. codegraph/indexing/__init__.py +24 -0
  54. codegraph/indexing/classifier.py +564 -0
  55. codegraph/indexing/indexer.py +2578 -0
  56. codegraph/indexing/models.py +427 -0
  57. codegraph/indexing/parser.py +3404 -0
  58. codegraph/indexing/pool.py +136 -0
  59. codegraph/indexing/scanner.py +211 -0
  60. codegraph/indexing/telemetry.py +326 -0
  61. codegraph/indexing/templates.py +418 -0
  62. codegraph/indexing/test_framework.py +116 -0
  63. codegraph/installer.py +1702 -0
  64. codegraph/interrogation.py +2355 -0
  65. codegraph/llm/__init__.py +3 -0
  66. codegraph/llm/base.py +15 -0
  67. codegraph/llm/context.py +20 -0
  68. codegraph/mcp/__init__.py +3 -0
  69. codegraph/mcp/server.py +1476 -0
  70. codegraph/mcp_diagnostics.py +882 -0
  71. codegraph/memory/__init__.py +3 -0
  72. codegraph/memory/store.py +46 -0
  73. codegraph/models.py +310 -0
  74. codegraph/monorepo.py +925 -0
  75. codegraph/observability.py +262 -0
  76. codegraph/optimizer.py +1014 -0
  77. codegraph/planner.py +417 -0
  78. codegraph/process_lifecycle.py +1153 -0
  79. codegraph/py.typed +1 -0
  80. codegraph/query_expansion.py +199 -0
  81. codegraph/ranking.py +408 -0
  82. codegraph/refactor/__init__.py +36 -0
  83. codegraph/refactor/models.py +114 -0
  84. codegraph/refactor/renamer.py +844 -0
  85. codegraph/refactor/transactions.py +414 -0
  86. codegraph/resolver.py +3968 -0
  87. codegraph/resources/__init__.py +46 -0
  88. codegraph/resources/cache.py +117 -0
  89. codegraph/resources/coalescer.py +83 -0
  90. codegraph/resources/debouncer.py +98 -0
  91. codegraph/resources/governor.py +219 -0
  92. codegraph/resources/monitor.py +102 -0
  93. codegraph/resources/policy.py +123 -0
  94. codegraph/retrieval_policy.py +320 -0
  95. codegraph/roblox.py +2174 -0
  96. codegraph/route_composer.py +363 -0
  97. codegraph/route_topology.py +172 -0
  98. codegraph/runtime/__init__.py +51 -0
  99. codegraph/runtime/collector.py +155 -0
  100. codegraph/runtime/db_watcher.py +191 -0
  101. codegraph/runtime/ingestor.py +692 -0
  102. codegraph/runtime/models.py +130 -0
  103. codegraph/runtime/port_inspector.py +304 -0
  104. codegraph/runtime/reconciliation.py +492 -0
  105. codegraph/runtime/runner.py +353 -0
  106. codegraph/runtime/trace_linker.py +175 -0
  107. codegraph/search/__init__.py +25 -0
  108. codegraph/search/hybrid.py +846 -0
  109. codegraph/search/semantic.py +28 -0
  110. codegraph/security/__init__.py +51 -0
  111. codegraph/security/paths.py +189 -0
  112. codegraph/security/redaction.py +633 -0
  113. codegraph/semantic_conflicts.py +270 -0
  114. codegraph/semantic_decorators.py +372 -0
  115. codegraph/structural_diff.py +542 -0
  116. codegraph/styles.py +144 -0
  117. codegraph/symbol_history.py +277 -0
  118. codegraph/target_resolver.py +348 -0
  119. codegraph/task.py +637 -0
  120. codegraph/tool_selection_eval.py +1158 -0
  121. codegraph/tsconfig.py +236 -0
  122. codegraph/ui/__init__.py +6 -0
  123. codegraph/ui/server.py +755 -0
  124. codegraph/watcher.py +323 -0
  125. codegraph_engine-3.0.0.dist-info/METADATA +859 -0
  126. codegraph_engine-3.0.0.dist-info/RECORD +130 -0
  127. codegraph_engine-3.0.0.dist-info/WHEEL +6 -0
  128. codegraph_engine-3.0.0.dist-info/entry_points.txt +2 -0
  129. codegraph_engine-3.0.0.dist-info/licenses/LICENSE +21 -0
  130. 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