codegraph-engine 3.0.0__2-py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codegraph/__init__.py +37 -0
- codegraph/agent.py +26 -0
- codegraph/agent_brain.py +2212 -0
- codegraph/agent_capabilities.py +4058 -0
- codegraph/agent_rules.py +531 -0
- codegraph/api_drift.py +455 -0
- codegraph/architecture.py +371 -0
- codegraph/async_queue/__init__.py +50 -0
- codegraph/async_queue/detectors/__init__.py +48 -0
- codegraph/async_queue/detectors/celery.py +160 -0
- codegraph/async_queue/detectors/kafka.py +168 -0
- codegraph/async_queue/detectors/rabbitmq.py +116 -0
- codegraph/async_queue/detectors/redis.py +130 -0
- codegraph/async_queue/detectors/sqs.py +135 -0
- codegraph/async_queue/models.py +161 -0
- codegraph/async_queue/otel_models.py +74 -0
- codegraph/async_queue/otel_parser.py +337 -0
- codegraph/async_queue/reconciler.py +427 -0
- codegraph/async_queue/scanner.py +291 -0
- codegraph/async_queue/traversal.py +447 -0
- codegraph/audit.py +106 -0
- codegraph/binding_resolver.py +616 -0
- codegraph/branch_comparison.py +197 -0
- codegraph/cache.py +95 -0
- codegraph/change_impact.py +547 -0
- codegraph/cli.py +2436 -0
- codegraph/cli_output.py +205 -0
- codegraph/config.py +45 -0
- codegraph/constraints.py +238 -0
- codegraph/context.py +2182 -0
- codegraph/context_freshness.py +351 -0
- codegraph/database/__init__.py +72 -0
- codegraph/database/extractor.py +3651 -0
- codegraph/database/interrogation.py +1494 -0
- codegraph/database/models.py +310 -0
- codegraph/database/schema_drift.py +862 -0
- codegraph/epistemic.py +200 -0
- codegraph/errors.py +282 -0
- codegraph/evidence/__init__.py +15 -0
- codegraph/evidence/citations.py +418 -0
- codegraph/evidence_contract.py +486 -0
- codegraph/frameworks.py +960 -0
- codegraph/frameworks_nestjs.py +502 -0
- codegraph/frameworks_nextjs.py +337 -0
- codegraph/frameworks_react.py +457 -0
- codegraph/freshness.py +335 -0
- codegraph/frontend_html.py +109 -0
- codegraph/git.py +362 -0
- codegraph/git_state.py +545 -0
- codegraph/graph/__init__.py +46 -0
- codegraph/graph/models.py +56 -0
- codegraph/graph/traversal.py +1993 -0
- codegraph/indexing/__init__.py +24 -0
- codegraph/indexing/classifier.py +564 -0
- codegraph/indexing/indexer.py +2578 -0
- codegraph/indexing/models.py +427 -0
- codegraph/indexing/parser.py +3404 -0
- codegraph/indexing/pool.py +136 -0
- codegraph/indexing/scanner.py +211 -0
- codegraph/indexing/telemetry.py +326 -0
- codegraph/indexing/templates.py +418 -0
- codegraph/indexing/test_framework.py +116 -0
- codegraph/installer.py +1702 -0
- codegraph/interrogation.py +2355 -0
- codegraph/llm/__init__.py +3 -0
- codegraph/llm/base.py +15 -0
- codegraph/llm/context.py +20 -0
- codegraph/mcp/__init__.py +3 -0
- codegraph/mcp/server.py +1476 -0
- codegraph/mcp_diagnostics.py +882 -0
- codegraph/memory/__init__.py +3 -0
- codegraph/memory/store.py +46 -0
- codegraph/models.py +310 -0
- codegraph/monorepo.py +925 -0
- codegraph/observability.py +262 -0
- codegraph/optimizer.py +1014 -0
- codegraph/planner.py +417 -0
- codegraph/process_lifecycle.py +1153 -0
- codegraph/py.typed +1 -0
- codegraph/query_expansion.py +199 -0
- codegraph/ranking.py +408 -0
- codegraph/refactor/__init__.py +36 -0
- codegraph/refactor/models.py +114 -0
- codegraph/refactor/renamer.py +844 -0
- codegraph/refactor/transactions.py +414 -0
- codegraph/resolver.py +3968 -0
- codegraph/resources/__init__.py +46 -0
- codegraph/resources/cache.py +117 -0
- codegraph/resources/coalescer.py +83 -0
- codegraph/resources/debouncer.py +98 -0
- codegraph/resources/governor.py +219 -0
- codegraph/resources/monitor.py +102 -0
- codegraph/resources/policy.py +123 -0
- codegraph/retrieval_policy.py +320 -0
- codegraph/roblox.py +2174 -0
- codegraph/route_composer.py +363 -0
- codegraph/route_topology.py +172 -0
- codegraph/runtime/__init__.py +51 -0
- codegraph/runtime/collector.py +155 -0
- codegraph/runtime/db_watcher.py +191 -0
- codegraph/runtime/ingestor.py +692 -0
- codegraph/runtime/models.py +130 -0
- codegraph/runtime/port_inspector.py +304 -0
- codegraph/runtime/reconciliation.py +492 -0
- codegraph/runtime/runner.py +353 -0
- codegraph/runtime/trace_linker.py +175 -0
- codegraph/search/__init__.py +25 -0
- codegraph/search/hybrid.py +846 -0
- codegraph/search/semantic.py +28 -0
- codegraph/security/__init__.py +51 -0
- codegraph/security/paths.py +189 -0
- codegraph/security/redaction.py +633 -0
- codegraph/semantic_conflicts.py +270 -0
- codegraph/semantic_decorators.py +372 -0
- codegraph/structural_diff.py +542 -0
- codegraph/styles.py +144 -0
- codegraph/symbol_history.py +277 -0
- codegraph/target_resolver.py +348 -0
- codegraph/task.py +637 -0
- codegraph/tool_selection_eval.py +1158 -0
- codegraph/tsconfig.py +236 -0
- codegraph/ui/__init__.py +6 -0
- codegraph/ui/server.py +755 -0
- codegraph/watcher.py +323 -0
- codegraph_engine-3.0.0.dist-info/METADATA +868 -0
- codegraph_engine-3.0.0.dist-info/RECORD +130 -0
- codegraph_engine-3.0.0.dist-info/WHEEL +6 -0
- codegraph_engine-3.0.0.dist-info/entry_points.txt +2 -0
- codegraph_engine-3.0.0.dist-info/licenses/LICENSE +21 -0
- codegraph_engine-3.0.0.dist-info/top_level.txt +1 -0
codegraph/optimizer.py
ADDED
|
@@ -0,0 +1,1014 @@
|
|
|
1
|
+
"""Global Token Budget Optimizer, Redundancy Control, Evidence Compression, and Coverage Engine.
|
|
2
|
+
|
|
3
|
+
Maximizes evidence quality, relationship coverage, freshness, and diversity of useful
|
|
4
|
+
context under strict deterministic budget constraints while eliminating redundant chunks
|
|
5
|
+
and compressing repeated evidence without weakening epistemic guarantees.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from codegraph.task import TaskSpec
|
|
13
|
+
|
|
14
|
+
# Reason codes for selected context candidates
|
|
15
|
+
REASON_DIRECT_TARGET = "DIRECT_TARGET"
|
|
16
|
+
REASON_DIRECT_CALLER = "DIRECT_CALLER"
|
|
17
|
+
REASON_VERIFIED_CALLEE = "VERIFIED_CALLEE"
|
|
18
|
+
REASON_RELEVANT_ROUTE = "RELEVANT_ROUTE"
|
|
19
|
+
REASON_RELATED_TEST = "RELATED_TEST"
|
|
20
|
+
REASON_PACKAGE_OWNER = "PACKAGE_OWNER"
|
|
21
|
+
REASON_DEPENDENCY_PACKAGE = "DEPENDENCY_PACKAGE"
|
|
22
|
+
REASON_HIGH_VALUE_EVIDENCE = "HIGH_VALUE_EVIDENCE"
|
|
23
|
+
REASON_COVERAGE_FILL = "COVERAGE_FILL"
|
|
24
|
+
REASON_AMBIGUITY_PRESERVED = "AMBIGUITY_PRESERVED"
|
|
25
|
+
|
|
26
|
+
_EVIDENCE_CLASS_RANK = {
|
|
27
|
+
"AST_VERIFIED": 5,
|
|
28
|
+
"FRAMEWORK_VERIFIED": 4,
|
|
29
|
+
"DATAFLOW_VERIFIED": 3,
|
|
30
|
+
"POSSIBLE": 2,
|
|
31
|
+
"UNKNOWN": 1,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_CONFIDENCE_RANK = {
|
|
35
|
+
"HIGH": 3,
|
|
36
|
+
"MEDIUM": 2,
|
|
37
|
+
"LOW": 1,
|
|
38
|
+
"UNKNOWN": 0,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Hard upper safety limits enforced even when omitted or exceeded
|
|
43
|
+
HARD_MAX_TOKENS = 20_000
|
|
44
|
+
HARD_MAX_FILES = 40
|
|
45
|
+
HARD_MAX_LINES = 1_500
|
|
46
|
+
HARD_MAX_SYMBOLS = 50
|
|
47
|
+
HARD_MAX_RELATIONSHIPS = 60
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class ContextBudgetSpec:
|
|
52
|
+
"""Explicit deterministic context budget bounds."""
|
|
53
|
+
|
|
54
|
+
max_tokens: int = 20_000
|
|
55
|
+
max_symbols: int = 25
|
|
56
|
+
max_relationships: int = 30
|
|
57
|
+
max_files: int = 25
|
|
58
|
+
max_lines: int = 500
|
|
59
|
+
max_depth: int = 3
|
|
60
|
+
max_package_depth: int = 2
|
|
61
|
+
|
|
62
|
+
def as_dict(self) -> dict[str, int]:
|
|
63
|
+
return {
|
|
64
|
+
"max_tokens": self.max_tokens,
|
|
65
|
+
"max_symbols": self.max_symbols,
|
|
66
|
+
"max_relationships": self.max_relationships,
|
|
67
|
+
"max_files": self.max_files,
|
|
68
|
+
"max_lines": self.max_lines,
|
|
69
|
+
"max_depth": self.max_depth,
|
|
70
|
+
"max_package_depth": self.max_package_depth,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class ContextBudget:
|
|
76
|
+
candidate_tokens: int
|
|
77
|
+
selected_tokens: int
|
|
78
|
+
reduction_ratio: float
|
|
79
|
+
budget_limit: int
|
|
80
|
+
rejections: tuple[dict[str, object], ...] = ()
|
|
81
|
+
candidate_count: int = 0
|
|
82
|
+
selected_count: int = 0
|
|
83
|
+
candidate_reduction_ratio: float = 0.0
|
|
84
|
+
selected_reduction_ratio: float = 0.0
|
|
85
|
+
budget_utilization: float = 0.0
|
|
86
|
+
coverage_score: float = 0.0
|
|
87
|
+
redundancy_ratio: float = 0.0
|
|
88
|
+
evidence_density: float = 0.0
|
|
89
|
+
direct_file_reads: int = 0
|
|
90
|
+
direct_file_lines: int = 0
|
|
91
|
+
selected_files: int = 0
|
|
92
|
+
selected_lines: int = 0
|
|
93
|
+
truncated: bool = False
|
|
94
|
+
tool_calls: int = 1
|
|
95
|
+
max_symbols: int = 25
|
|
96
|
+
max_relationships: int = 30
|
|
97
|
+
max_files: int = 25
|
|
98
|
+
max_lines: int = 500
|
|
99
|
+
max_depth: int = 3
|
|
100
|
+
max_package_depth: int = 2
|
|
101
|
+
covered_dimensions: tuple[str, ...] = ()
|
|
102
|
+
missing_dimensions: tuple[str, ...] = ()
|
|
103
|
+
|
|
104
|
+
def as_dict(self) -> dict[str, object]:
|
|
105
|
+
return {
|
|
106
|
+
"candidate_count": self.candidate_count,
|
|
107
|
+
"selected_count": self.selected_count,
|
|
108
|
+
"candidate_tokens": self.candidate_tokens,
|
|
109
|
+
"selected_tokens": self.selected_tokens,
|
|
110
|
+
"selected_files": self.selected_files,
|
|
111
|
+
"selected_lines": self.selected_lines,
|
|
112
|
+
"truncated": self.truncated,
|
|
113
|
+
"reduction_ratio": round(self.reduction_ratio, 4),
|
|
114
|
+
"candidate_reduction_ratio": round(self.candidate_reduction_ratio, 4),
|
|
115
|
+
"selected_reduction_ratio": round(self.selected_reduction_ratio, 4),
|
|
116
|
+
"budget_limit": self.budget_limit,
|
|
117
|
+
"budget_utilization": round(self.budget_utilization, 4),
|
|
118
|
+
"coverage_score": round(self.coverage_score, 4),
|
|
119
|
+
"redundancy_ratio": round(self.redundancy_ratio, 4),
|
|
120
|
+
"evidence_density": round(self.evidence_density, 4),
|
|
121
|
+
"direct_file_reads": self.direct_file_reads,
|
|
122
|
+
"direct_file_lines": self.direct_file_lines,
|
|
123
|
+
"tool_calls": self.tool_calls,
|
|
124
|
+
"max_symbols": self.max_symbols,
|
|
125
|
+
"max_relationships": self.max_relationships,
|
|
126
|
+
"max_files": self.max_files,
|
|
127
|
+
"max_lines": self.max_lines,
|
|
128
|
+
"max_depth": self.max_depth,
|
|
129
|
+
"max_package_depth": self.max_package_depth,
|
|
130
|
+
"covered_dimensions": list(self.covered_dimensions),
|
|
131
|
+
"missing_dimensions": list(self.missing_dimensions),
|
|
132
|
+
"rejections": list(self.rejections),
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass(frozen=True)
|
|
137
|
+
class ContextCandidate:
|
|
138
|
+
candidate_id: str
|
|
139
|
+
canonical_id: str | None
|
|
140
|
+
source_type: str
|
|
141
|
+
estimated_tokens: int
|
|
142
|
+
relevance: float
|
|
143
|
+
evidence_quality: float
|
|
144
|
+
freshness: float
|
|
145
|
+
relationship_value: float
|
|
146
|
+
coverage_value: float
|
|
147
|
+
redundancy_group: str | None
|
|
148
|
+
status: str
|
|
149
|
+
confidence: str
|
|
150
|
+
reason_code: str = ""
|
|
151
|
+
|
|
152
|
+
def as_dict(self) -> dict[str, object]:
|
|
153
|
+
return {
|
|
154
|
+
"candidate_id": self.candidate_id,
|
|
155
|
+
"canonical_id": self.canonical_id,
|
|
156
|
+
"source_type": self.source_type,
|
|
157
|
+
"estimated_tokens": self.estimated_tokens,
|
|
158
|
+
"relevance": self.relevance,
|
|
159
|
+
"evidence_quality": self.evidence_quality,
|
|
160
|
+
"freshness": self.freshness,
|
|
161
|
+
"relationship_value": self.relationship_value,
|
|
162
|
+
"coverage_value": self.coverage_value,
|
|
163
|
+
"redundancy_group": self.redundancy_group,
|
|
164
|
+
"status": self.status,
|
|
165
|
+
"confidence": self.confidence,
|
|
166
|
+
"reason_code": self.reason_code,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@dataclass
|
|
171
|
+
class CandidateContextItem:
|
|
172
|
+
item_id: str
|
|
173
|
+
file_path: str
|
|
174
|
+
start_line: int
|
|
175
|
+
end_line: int
|
|
176
|
+
canonical_id: str | None
|
|
177
|
+
estimated_tokens: int
|
|
178
|
+
relevance_score: float # 0.0 - 1.0
|
|
179
|
+
evidence_quality: float # 0.0 - 1.0
|
|
180
|
+
freshness: str # FRESH | PARTIALLY_STALE | STALE | UNKNOWN
|
|
181
|
+
coverage_layer: str # ENTRYPOINT | HANDLER | SERVICE | DATA | TEST | GIT | ARCHITECTURE | GENERAL
|
|
182
|
+
source_type: str # chunk | symbol | edge | test | git | route
|
|
183
|
+
snippet: str
|
|
184
|
+
confidence: str = "HIGH"
|
|
185
|
+
relationship_value: float = 0.5
|
|
186
|
+
epistemic_status: str = "FACT" # FACT | ASSUMPTION | INFERENCE | UNKNOWN | CONFLICT | POSSIBLE | AMBIGUOUS
|
|
187
|
+
coverage_value: float = 0.5
|
|
188
|
+
data: dict[str, Any] = field(default_factory=dict)
|
|
189
|
+
why_selected: list[str] = field(default_factory=list)
|
|
190
|
+
reason_code: str = ""
|
|
191
|
+
package_id: str | None = None
|
|
192
|
+
package_distance: int = 0 # 0=owner, 1=direct dep, 2=transitive, 99=unrelated
|
|
193
|
+
artifact_class: str = "SOURCE"
|
|
194
|
+
graph_distance: int = 0
|
|
195
|
+
task_dimension: str = ""
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def relevance(self) -> float:
|
|
199
|
+
return self.relevance_score
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def status(self) -> str:
|
|
203
|
+
return self.epistemic_status
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def redundancy_group(self) -> str:
|
|
207
|
+
return self.duplicate_key
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def duplicate_key(self) -> str:
|
|
211
|
+
"""Deterministic grouping key for redundancy detection by canonical identity."""
|
|
212
|
+
if self.canonical_id:
|
|
213
|
+
return f"sym:{self.canonical_id}"
|
|
214
|
+
sym = self.data.get("symbol")
|
|
215
|
+
if sym:
|
|
216
|
+
return f"sym:{self.file_path}::{sym}"
|
|
217
|
+
return f"loc:{self.file_path}:{self.start_line}"
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def line_count(self) -> int:
|
|
221
|
+
if self.snippet:
|
|
222
|
+
return max(1, len(self.snippet.splitlines()))
|
|
223
|
+
return max(1, self.end_line - self.start_line + 1)
|
|
224
|
+
|
|
225
|
+
def infer_dimension(self) -> str:
|
|
226
|
+
"""Determine the task coverage dimension represented by this candidate."""
|
|
227
|
+
if self.task_dimension:
|
|
228
|
+
return self.task_dimension
|
|
229
|
+
if self.epistemic_status in ("UNKNOWN", "CONFLICT", "POSSIBLE", "AMBIGUOUS"):
|
|
230
|
+
return "UNKNOWN"
|
|
231
|
+
rel = str(self.data.get("relationship") or "").upper()
|
|
232
|
+
reasons = self.data.get("reasons", [])
|
|
233
|
+
reason_codes = {getattr(r, "code", "") for r in reasons}
|
|
234
|
+
if any(c in reason_codes for c in ("EXACT_CANONICAL", "EXACT_QUALIFIED", "EXACT_SYMBOL")):
|
|
235
|
+
return "TARGET"
|
|
236
|
+
if self.coverage_layer == "ENTRYPOINT" or rel in ("HANDLED_BY", "ROUTES_TO", "MOUNTS", "ROUTE_HANDLER"):
|
|
237
|
+
return "ROUTE"
|
|
238
|
+
if self.coverage_layer == "TEST" or rel.startswith("TEST") or self.source_type == "test":
|
|
239
|
+
return "TEST"
|
|
240
|
+
if rel in ("INJECTS", "PROVIDES", "RESOLVES_DEPENDENCY", "CONFIGURES"):
|
|
241
|
+
return "PROVIDER"
|
|
242
|
+
if rel in ("REGISTERS", "DISPATCHES_TO", "EVENT_LISTENER", "TASK_HANDLER", "COMMAND_HANDLER"):
|
|
243
|
+
return "REGISTRATION"
|
|
244
|
+
if rel == "DEPENDS_ON_PACKAGE":
|
|
245
|
+
return "PACKAGE"
|
|
246
|
+
if "DIRECT_CALLER" in reason_codes or "POSSIBLE_CALLER" in reason_codes or rel in ("CALLERS", "CALLED_BY", "POSSIBLE_CALLS"):
|
|
247
|
+
return "CALLER"
|
|
248
|
+
if "DIRECT_CALLEE" in reason_codes or rel == "CALLS":
|
|
249
|
+
return "CALLEE"
|
|
250
|
+
if self.coverage_layer == "GIT":
|
|
251
|
+
return "GIT"
|
|
252
|
+
if self.coverage_layer in ("SERVICE", "DATA", "HANDLER"):
|
|
253
|
+
return self.coverage_layer
|
|
254
|
+
return "TARGET" if self.relevance_score >= 0.7 else "GENERAL"
|
|
255
|
+
|
|
256
|
+
def infer_reason_code(self, is_coverage_fill: bool = False) -> str:
|
|
257
|
+
"""Assign an explainable reason code for selection."""
|
|
258
|
+
if self.reason_code:
|
|
259
|
+
return self.reason_code
|
|
260
|
+
if self.epistemic_status in ("UNKNOWN", "CONFLICT", "POSSIBLE", "AMBIGUOUS"):
|
|
261
|
+
return REASON_AMBIGUITY_PRESERVED
|
|
262
|
+
dim = self.infer_dimension()
|
|
263
|
+
if dim == "TARGET":
|
|
264
|
+
return REASON_DIRECT_TARGET
|
|
265
|
+
if dim == "ROUTE":
|
|
266
|
+
return REASON_RELEVANT_ROUTE
|
|
267
|
+
if dim == "TEST":
|
|
268
|
+
return REASON_RELATED_TEST
|
|
269
|
+
if dim == "CALLER":
|
|
270
|
+
return REASON_DIRECT_CALLER
|
|
271
|
+
if dim == "CALLEE":
|
|
272
|
+
return REASON_VERIFIED_CALLEE
|
|
273
|
+
if dim in ("PROVIDER", "REGISTRATION"):
|
|
274
|
+
return REASON_HIGH_VALUE_EVIDENCE
|
|
275
|
+
if self.package_id and self.package_distance == 0:
|
|
276
|
+
return REASON_PACKAGE_OWNER
|
|
277
|
+
if self.package_id and self.package_distance in (1, 2):
|
|
278
|
+
return REASON_DEPENDENCY_PACKAGE
|
|
279
|
+
if is_coverage_fill:
|
|
280
|
+
return REASON_COVERAGE_FILL
|
|
281
|
+
return REASON_HIGH_VALUE_EVIDENCE
|
|
282
|
+
|
|
283
|
+
def to_context_candidate(self) -> ContextCandidate:
|
|
284
|
+
f_mult = _freshness_multiplier(self.freshness)
|
|
285
|
+
return ContextCandidate(
|
|
286
|
+
candidate_id=self.item_id,
|
|
287
|
+
canonical_id=self.canonical_id,
|
|
288
|
+
source_type=self.source_type,
|
|
289
|
+
estimated_tokens=self.estimated_tokens,
|
|
290
|
+
relevance=self.relevance_score,
|
|
291
|
+
evidence_quality=self.evidence_quality,
|
|
292
|
+
freshness=f_mult,
|
|
293
|
+
relationship_value=self.relationship_value,
|
|
294
|
+
coverage_value=self.coverage_value,
|
|
295
|
+
redundancy_group=self.duplicate_key,
|
|
296
|
+
status=self.epistemic_status,
|
|
297
|
+
confidence=self.confidence,
|
|
298
|
+
reason_code=self.reason_code or self.infer_reason_code(),
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _freshness_multiplier(freshness: str) -> float:
|
|
303
|
+
if freshness == "FRESH":
|
|
304
|
+
return 1.0
|
|
305
|
+
if freshness == "PARTIALLY_STALE":
|
|
306
|
+
return 0.7
|
|
307
|
+
if freshness == "STALE":
|
|
308
|
+
return 0.3
|
|
309
|
+
return 0.5
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _confidence_penalty(confidence: str) -> float:
|
|
313
|
+
if confidence == "HIGH":
|
|
314
|
+
return 0.0
|
|
315
|
+
if confidence == "MEDIUM":
|
|
316
|
+
return 0.1
|
|
317
|
+
return 0.25
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _epistemic_multiplier(status: str) -> float:
|
|
321
|
+
if status == "FACT":
|
|
322
|
+
return 1.0
|
|
323
|
+
if status == "ASSUMPTION":
|
|
324
|
+
return 0.85
|
|
325
|
+
if status == "INFERENCE":
|
|
326
|
+
return 0.70
|
|
327
|
+
if status == "UNKNOWN":
|
|
328
|
+
return 0.40
|
|
329
|
+
if status == "CONFLICT":
|
|
330
|
+
return 0.30
|
|
331
|
+
return 0.70
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _artifact_penalty(artifact_class: str) -> float:
|
|
335
|
+
cat = artifact_class.upper()
|
|
336
|
+
if cat in ("MINIFIED", "BUNDLE", "BUILD_ARTIFACT", "BINARY"):
|
|
337
|
+
return 0.50
|
|
338
|
+
if cat == "VENDOR":
|
|
339
|
+
return 0.40
|
|
340
|
+
if cat == "GENERATED":
|
|
341
|
+
return 0.20
|
|
342
|
+
return 0.0
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _package_adjustment(package_distance: int) -> float:
|
|
346
|
+
if package_distance == 0:
|
|
347
|
+
return 0.08 # target package bonus
|
|
348
|
+
if package_distance == 1:
|
|
349
|
+
return 0.04 # direct dependency/dependent bonus
|
|
350
|
+
if package_distance == 2:
|
|
351
|
+
return 0.0
|
|
352
|
+
if package_distance >= 99:
|
|
353
|
+
return -0.15 # unrelated workspace package penalty
|
|
354
|
+
return -0.05 * (package_distance - 1)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def compute_item_utility(
|
|
358
|
+
item: CandidateContextItem,
|
|
359
|
+
selected_layers: set[str],
|
|
360
|
+
selected_duplicate_keys: set[str],
|
|
361
|
+
selected_dimensions: set[str] | None = None,
|
|
362
|
+
) -> float:
|
|
363
|
+
"""Calculate deterministic utility score for a candidate context item."""
|
|
364
|
+
fresh_factor = _freshness_multiplier(item.freshness)
|
|
365
|
+
conf_penalty = _confidence_penalty(item.confidence)
|
|
366
|
+
epistemic_factor = _epistemic_multiplier(item.epistemic_status)
|
|
367
|
+
art_penalty = _artifact_penalty(item.artifact_class)
|
|
368
|
+
pkg_adj = _package_adjustment(item.package_distance) if item.package_id else 0.0
|
|
369
|
+
dist_penalty = min(0.20, 0.04 * max(0, item.graph_distance))
|
|
370
|
+
|
|
371
|
+
# Coverage reward: boost item if it introduces a required layer or dimension not yet represented
|
|
372
|
+
dim = item.infer_dimension()
|
|
373
|
+
layer_unseen = item.coverage_layer not in selected_layers
|
|
374
|
+
dim_unseen = selected_dimensions is not None and dim not in selected_dimensions and dim != "GENERAL"
|
|
375
|
+
coverage_factor = 1.3 if (layer_unseen or dim_unseen) else 1.0
|
|
376
|
+
|
|
377
|
+
# Priority boost for task-critical items over tangential ones
|
|
378
|
+
priority_dim_boost = 0.0
|
|
379
|
+
if dim == "TARGET" or item.relevance_score >= 0.80:
|
|
380
|
+
priority_dim_boost = 0.20
|
|
381
|
+
elif dim in ("ROUTE", "CALLER"):
|
|
382
|
+
priority_dim_boost = 0.12
|
|
383
|
+
elif dim == "CALLEE":
|
|
384
|
+
priority_dim_boost = 0.08
|
|
385
|
+
elif dim in ("TEST", "GIT", "PACKAGE", "GENERAL") and item.relevance_score < 0.50:
|
|
386
|
+
priority_dim_boost = -0.08
|
|
387
|
+
|
|
388
|
+
# Redundancy penalty: heavily discount items sharing duplicate keys with already selected items
|
|
389
|
+
redundancy_penalty = 0.5 if item.duplicate_key in selected_duplicate_keys else 0.0
|
|
390
|
+
|
|
391
|
+
utility = (
|
|
392
|
+
(item.relevance_score * 0.40)
|
|
393
|
+
+ (item.evidence_quality * 0.20)
|
|
394
|
+
+ (item.relationship_value * 0.15)
|
|
395
|
+
+ (fresh_factor * 0.15)
|
|
396
|
+
+ (epistemic_factor * 0.10)
|
|
397
|
+
+ pkg_adj
|
|
398
|
+
+ priority_dim_boost
|
|
399
|
+
) * coverage_factor
|
|
400
|
+
|
|
401
|
+
final_score = max(0.01, utility - redundancy_penalty - conf_penalty - art_penalty - dist_penalty)
|
|
402
|
+
return round(final_score, 4)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _is_excluded(item: CandidateContextItem, task_spec: TaskSpec | None) -> bool:
|
|
406
|
+
"""Return True if this candidate violates a hard exclusion constraint.
|
|
407
|
+
|
|
408
|
+
Hierarchical rules:
|
|
409
|
+
- "AuthService" excludes AuthService AND AuthService.login AND AuthService.*
|
|
410
|
+
- "src/admin_panel" excludes any file_path starting with that prefix
|
|
411
|
+
- Canonical-ID comparison preferred over raw string matching
|
|
412
|
+
"""
|
|
413
|
+
if not task_spec or not task_spec.exclusions:
|
|
414
|
+
return False
|
|
415
|
+
item_sym = item.canonical_id or str(item.data.get("symbol") or "")
|
|
416
|
+
item_file = item.file_path or ""
|
|
417
|
+
for excl in task_spec.exclusions:
|
|
418
|
+
if not excl:
|
|
419
|
+
continue
|
|
420
|
+
# ── exact symbol match ───────────────────────────────────────────
|
|
421
|
+
if item_sym and (item_sym == excl):
|
|
422
|
+
return True
|
|
423
|
+
# ── hierarchical: AuthService excludes AuthService.anything ──────
|
|
424
|
+
if item_sym and (
|
|
425
|
+
item_sym.startswith(excl + ".") or item_sym.startswith(excl + ":")
|
|
426
|
+
):
|
|
427
|
+
return True
|
|
428
|
+
# ── exact file match ─────────────────────────────────────────────
|
|
429
|
+
if item_file and item_file == excl:
|
|
430
|
+
return True
|
|
431
|
+
# ── file path prefix (module/directory exclusion) ────────────────
|
|
432
|
+
if item_file and (item_file.startswith(excl + "/") or item_file.startswith(excl + "\\")):
|
|
433
|
+
return True
|
|
434
|
+
# ── file contains the exclusion path segment ─────────────────────
|
|
435
|
+
if item_file and len(excl) >= 4 and excl in item_file:
|
|
436
|
+
return True
|
|
437
|
+
# ── substring match on symbol (kept for backward compat, len >= 4) ─
|
|
438
|
+
if item_sym and len(excl) >= 4 and excl in item_sym:
|
|
439
|
+
return True
|
|
440
|
+
return False
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def select_bounded_snippet(
|
|
444
|
+
content: str,
|
|
445
|
+
start_line: int = 1,
|
|
446
|
+
end_line: int | None = None,
|
|
447
|
+
max_lines: int = 35,
|
|
448
|
+
max_chars: int = 800,
|
|
449
|
+
) -> tuple[str, int, int]:
|
|
450
|
+
"""Extract a deterministic, bounded source snippet without dumping entire files.
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
(bounded_snippet, requested_lines, returned_lines)
|
|
454
|
+
"""
|
|
455
|
+
if not content:
|
|
456
|
+
return "", 0, 0
|
|
457
|
+
lines = content.splitlines()
|
|
458
|
+
total_requested = len(lines)
|
|
459
|
+
if total_requested == 0:
|
|
460
|
+
return "", 0, 0
|
|
461
|
+
|
|
462
|
+
bounded_lines = lines[:max(1, max_lines)]
|
|
463
|
+
text = "\n".join(bounded_lines)
|
|
464
|
+
if len(text) > max_chars:
|
|
465
|
+
text = text[:max_chars]
|
|
466
|
+
bounded_lines = text.splitlines()
|
|
467
|
+
|
|
468
|
+
returned_lines = len(bounded_lines)
|
|
469
|
+
return text, total_requested, returned_lines
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def compress_relationships(
|
|
473
|
+
relationships: list[Any],
|
|
474
|
+
max_relationships: int = 30,
|
|
475
|
+
) -> tuple[list[Any], dict[str, Any]]:
|
|
476
|
+
"""Deterministically compress duplicate relationship findings while preserving traceability.
|
|
477
|
+
|
|
478
|
+
Rules:
|
|
479
|
+
- Group strictly by (source, target, relationship). Never merge different relationship types.
|
|
480
|
+
- Keep the strongest evidence class and highest confidence as the primary record.
|
|
481
|
+
- Retain concise supporting locations when multiple locations prove the same direct fact.
|
|
482
|
+
- Never drop the only evidence for a claim.
|
|
483
|
+
- Never convert UNKNOWN into a positive relationship or POSSIBLE into verified.
|
|
484
|
+
"""
|
|
485
|
+
if not relationships:
|
|
486
|
+
return [], {"raw_count": 0, "compressed_count": 0, "duplicates_merged": 0}
|
|
487
|
+
|
|
488
|
+
from codegraph.evidence_contract import validate_relationship_record
|
|
489
|
+
|
|
490
|
+
grouped: dict[tuple[str, str, str], list[Any]] = {}
|
|
491
|
+
order_keys: list[tuple[str, str, str]] = []
|
|
492
|
+
|
|
493
|
+
for r in relationships:
|
|
494
|
+
validated = validate_relationship_record(r)
|
|
495
|
+
if isinstance(r, dict):
|
|
496
|
+
r["relationship"] = validated.relationship
|
|
497
|
+
r["evidence_class"] = validated.evidence_class
|
|
498
|
+
r["confidence"] = validated.confidence
|
|
499
|
+
r["status"] = validated.status
|
|
500
|
+
key = (validated.source, validated.target, validated.relationship)
|
|
501
|
+
if key not in grouped:
|
|
502
|
+
grouped[key] = []
|
|
503
|
+
order_keys.append(key)
|
|
504
|
+
grouped[key].append(r)
|
|
505
|
+
|
|
506
|
+
compressed: list[Any] = []
|
|
507
|
+
duplicates_merged = 0
|
|
508
|
+
|
|
509
|
+
for key in order_keys:
|
|
510
|
+
group = grouped[key]
|
|
511
|
+
if len(group) == 1:
|
|
512
|
+
compressed.append(group[0])
|
|
513
|
+
continue
|
|
514
|
+
|
|
515
|
+
duplicates_merged += len(group) - 1
|
|
516
|
+
|
|
517
|
+
# Sort group to pick the strongest primary record deterministically
|
|
518
|
+
def _rel_rank(item: Any) -> tuple[int, int, str, int]:
|
|
519
|
+
conf = str(getattr(item, "confidence", "HIGH") if not isinstance(item, dict) else item.get("confidence", "HIGH")).upper()
|
|
520
|
+
ev_cls = str(getattr(item, "evidence_class", "") if not isinstance(item, dict) else item.get("evidence_class", "")).upper()
|
|
521
|
+
f_path = str(getattr(item, "file", "") or (item.get("file", "") if isinstance(item, dict) else ""))
|
|
522
|
+
s_line = int(getattr(item, "start_line", 0) or (item.get("start_line", 0) if isinstance(item, dict) else 0) or 0)
|
|
523
|
+
return (
|
|
524
|
+
-_EVIDENCE_CLASS_RANK.get(ev_cls, 0),
|
|
525
|
+
-_CONFIDENCE_RANK.get(conf, 0),
|
|
526
|
+
f_path,
|
|
527
|
+
s_line,
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
sorted_group = sorted(group, key=_rel_rank)
|
|
531
|
+
primary = sorted_group[0]
|
|
532
|
+
|
|
533
|
+
# Collect concise supporting locations (up to 4 additional locations)
|
|
534
|
+
supporting_locs: list[str] = []
|
|
535
|
+
for extra in sorted_group[1:]:
|
|
536
|
+
ef = str(getattr(extra, "file", "") or (extra.get("file", "") if isinstance(extra, dict) else ""))
|
|
537
|
+
el = getattr(extra, "start_line", None) if not isinstance(extra, dict) else extra.get("start_line")
|
|
538
|
+
loc_str = f"{ef}:{el}" if ef and el else ef
|
|
539
|
+
if loc_str and loc_str not in supporting_locs:
|
|
540
|
+
supporting_locs.append(loc_str)
|
|
541
|
+
|
|
542
|
+
if hasattr(primary, "supporting_locations"):
|
|
543
|
+
try:
|
|
544
|
+
object.__setattr__(primary, "supporting_locations", supporting_locs[:4])
|
|
545
|
+
object.__setattr__(primary, "occurrence_count", len(group))
|
|
546
|
+
except Exception:
|
|
547
|
+
pass
|
|
548
|
+
elif isinstance(primary, dict):
|
|
549
|
+
primary["supporting_locations"] = supporting_locs[:4]
|
|
550
|
+
primary["occurrence_count"] = len(group)
|
|
551
|
+
|
|
552
|
+
compressed.append(primary)
|
|
553
|
+
|
|
554
|
+
# Sort compressed relationships deterministically:
|
|
555
|
+
# 1. Confidence rank desc
|
|
556
|
+
# 2. Source, relationship, target ascending
|
|
557
|
+
def _final_rel_sort(item: Any) -> tuple[int, str, str, str]:
|
|
558
|
+
conf = str(getattr(item, "confidence", "HIGH") if not isinstance(item, dict) else item.get("confidence", "HIGH")).upper()
|
|
559
|
+
src = str(getattr(item, "source", "") if not isinstance(item, dict) else item.get("source", ""))
|
|
560
|
+
rel = str(getattr(item, "relationship", "") if not isinstance(item, dict) else item.get("relationship", ""))
|
|
561
|
+
tgt = str(getattr(item, "target", "") if not isinstance(item, dict) else item.get("target", ""))
|
|
562
|
+
return (-_CONFIDENCE_RANK.get(conf, 0), src, rel, tgt)
|
|
563
|
+
|
|
564
|
+
compressed.sort(key=_final_rel_sort)
|
|
565
|
+
bounded = compressed[:max(1, max_relationships)]
|
|
566
|
+
|
|
567
|
+
stats = {
|
|
568
|
+
"raw_count": len(relationships),
|
|
569
|
+
"compressed_count": len(bounded),
|
|
570
|
+
"duplicates_merged": duplicates_merged,
|
|
571
|
+
}
|
|
572
|
+
return bounded, stats
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def optimize_context_budget(
|
|
576
|
+
candidates: list[CandidateContextItem],
|
|
577
|
+
token_budget: int,
|
|
578
|
+
task_spec: TaskSpec | None = None,
|
|
579
|
+
budget_spec: ContextBudgetSpec | None = None,
|
|
580
|
+
required_dimensions: tuple[str, ...] = ("TARGET", "SERVICE", "TEST"),
|
|
581
|
+
verified_claims_count: int = 0,
|
|
582
|
+
tool_calls: int = 1,
|
|
583
|
+
) -> tuple[list[CandidateContextItem], ContextBudget]:
|
|
584
|
+
"""Deterministically select the highest-utility, coverage-preserving subset of candidates."""
|
|
585
|
+
raw_max_symbols = budget_spec.max_symbols if budget_spec else 25
|
|
586
|
+
raw_max_relationships = budget_spec.max_relationships if budget_spec else 30
|
|
587
|
+
raw_max_files = budget_spec.max_files if budget_spec else 25
|
|
588
|
+
raw_max_lines = budget_spec.max_lines if budget_spec else 500
|
|
589
|
+
max_depth = budget_spec.max_depth if budget_spec else 3
|
|
590
|
+
max_package_depth = budget_spec.max_package_depth if budget_spec else 2
|
|
591
|
+
|
|
592
|
+
was_clamped = (
|
|
593
|
+
token_budget > HARD_MAX_TOKENS
|
|
594
|
+
or raw_max_symbols > HARD_MAX_SYMBOLS
|
|
595
|
+
or raw_max_relationships > HARD_MAX_RELATIONSHIPS
|
|
596
|
+
or raw_max_files > HARD_MAX_FILES
|
|
597
|
+
or raw_max_lines > HARD_MAX_LINES
|
|
598
|
+
)
|
|
599
|
+
token_budget = min(token_budget, HARD_MAX_TOKENS)
|
|
600
|
+
max_symbols = max(1, min(raw_max_symbols, HARD_MAX_SYMBOLS))
|
|
601
|
+
max_relationships = max(1, min(raw_max_relationships, HARD_MAX_RELATIONSHIPS))
|
|
602
|
+
max_files = max(1, min(raw_max_files, HARD_MAX_FILES))
|
|
603
|
+
max_lines = max(1, min(raw_max_lines, HARD_MAX_LINES))
|
|
604
|
+
|
|
605
|
+
if not candidates or token_budget <= 0 or (budget_spec is not None and (budget_spec.max_files <= 0 or budget_spec.max_lines <= 0)):
|
|
606
|
+
return [], ContextBudget(
|
|
607
|
+
candidate_tokens=sum(c.estimated_tokens for c in candidates) if candidates else 0,
|
|
608
|
+
selected_tokens=0,
|
|
609
|
+
reduction_ratio=1.0 if candidates else 0.0,
|
|
610
|
+
budget_limit=max(0, token_budget),
|
|
611
|
+
candidate_count=len(candidates) if candidates else 0,
|
|
612
|
+
selected_count=0,
|
|
613
|
+
candidate_reduction_ratio=1.0 if candidates else 0.0,
|
|
614
|
+
selected_reduction_ratio=1.0 if candidates else 0.0,
|
|
615
|
+
budget_utilization=0.0,
|
|
616
|
+
coverage_score=0.0,
|
|
617
|
+
redundancy_ratio=0.0,
|
|
618
|
+
evidence_density=0.0,
|
|
619
|
+
direct_file_reads=0,
|
|
620
|
+
direct_file_lines=0,
|
|
621
|
+
selected_files=0,
|
|
622
|
+
selected_lines=0,
|
|
623
|
+
truncated=bool(candidates),
|
|
624
|
+
tool_calls=tool_calls,
|
|
625
|
+
max_symbols=max_symbols,
|
|
626
|
+
max_relationships=max_relationships,
|
|
627
|
+
max_files=max_files,
|
|
628
|
+
max_lines=max_lines,
|
|
629
|
+
max_depth=max_depth,
|
|
630
|
+
max_package_depth=max_package_depth,
|
|
631
|
+
covered_dimensions=(),
|
|
632
|
+
missing_dimensions=required_dimensions,
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
candidate_count_total = len(candidates)
|
|
636
|
+
candidate_tokens_total = sum(c.estimated_tokens for c in candidates)
|
|
637
|
+
|
|
638
|
+
# Initial deterministic sort of candidates:
|
|
639
|
+
# 1. Relevance score descending
|
|
640
|
+
# 2. Evidence quality descending
|
|
641
|
+
# 3. Package distance ascending
|
|
642
|
+
# 4. Canonical ID or file path ascending (stable tie-breaker)
|
|
643
|
+
# 5. Start line ascending
|
|
644
|
+
sorted_candidates = sorted(
|
|
645
|
+
candidates,
|
|
646
|
+
key=lambda x: (
|
|
647
|
+
-x.relevance_score,
|
|
648
|
+
-x.evidence_quality,
|
|
649
|
+
x.package_distance,
|
|
650
|
+
x.canonical_id or x.file_path,
|
|
651
|
+
x.start_line,
|
|
652
|
+
),
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
selected: list[CandidateContextItem] = []
|
|
656
|
+
selected_layers: set[str] = set()
|
|
657
|
+
selected_dimensions: set[str] = set()
|
|
658
|
+
selected_duplicate_keys: set[str] = set()
|
|
659
|
+
selected_files: set[str] = set()
|
|
660
|
+
selected_tokens = 0
|
|
661
|
+
selected_lines = 0
|
|
662
|
+
selected_symbols_count = 0
|
|
663
|
+
snippet_trimmed = False
|
|
664
|
+
|
|
665
|
+
def _is_epistemic_uncertainty(item: CandidateContextItem) -> bool:
|
|
666
|
+
ev_cls = str(item.data.get("evidence_class", "")).upper()
|
|
667
|
+
return (
|
|
668
|
+
item.epistemic_status in ("UNKNOWN", "CONFLICT", "POSSIBLE", "AMBIGUOUS")
|
|
669
|
+
or ev_cls in ("UNKNOWN", "POSSIBLE")
|
|
670
|
+
or item.source_type in ("uncertainty", "finding")
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
def _can_fit(item: CandidateContextItem, is_epistemic_uncertainty: bool = False) -> bool:
|
|
674
|
+
if item.graph_distance > max_depth:
|
|
675
|
+
return False
|
|
676
|
+
if item.package_distance != 99 and item.package_distance > max_package_depth:
|
|
677
|
+
return False
|
|
678
|
+
if selected_tokens + item.estimated_tokens > token_budget:
|
|
679
|
+
return False
|
|
680
|
+
if not is_epistemic_uncertainty and selected_symbols_count >= max_symbols:
|
|
681
|
+
return False
|
|
682
|
+
if (
|
|
683
|
+
not is_epistemic_uncertainty
|
|
684
|
+
and item.file_path
|
|
685
|
+
and item.file_path not in selected_files
|
|
686
|
+
and len(selected_files) >= max_files
|
|
687
|
+
):
|
|
688
|
+
return False
|
|
689
|
+
if (
|
|
690
|
+
not is_epistemic_uncertainty
|
|
691
|
+
and selected_lines + item.line_count > max_lines
|
|
692
|
+
):
|
|
693
|
+
return False
|
|
694
|
+
return True
|
|
695
|
+
|
|
696
|
+
def _trim_to_fit(item: CandidateContextItem, is_epistemic_uncertainty: bool = False) -> bool:
|
|
697
|
+
nonlocal snippet_trimmed
|
|
698
|
+
if item.graph_distance > max_depth:
|
|
699
|
+
return False
|
|
700
|
+
if item.package_distance != 99 and item.package_distance > max_package_depth:
|
|
701
|
+
return False
|
|
702
|
+
if not is_epistemic_uncertainty and selected_symbols_count >= max_symbols:
|
|
703
|
+
return False
|
|
704
|
+
if (
|
|
705
|
+
not is_epistemic_uncertainty
|
|
706
|
+
and item.file_path
|
|
707
|
+
and item.file_path not in selected_files
|
|
708
|
+
and len(selected_files) >= max_files
|
|
709
|
+
):
|
|
710
|
+
return False
|
|
711
|
+
rem_tokens = token_budget - selected_tokens
|
|
712
|
+
rem_lines = max_lines - selected_lines if not is_epistemic_uncertainty else max(1, max_lines - selected_lines)
|
|
713
|
+
if rem_tokens <= 0 or rem_lines <= 0:
|
|
714
|
+
return False
|
|
715
|
+
max_chars = max(4, rem_tokens * 4)
|
|
716
|
+
lines = (item.snippet or "").splitlines()
|
|
717
|
+
bounded_lines = lines[:rem_lines] if lines else []
|
|
718
|
+
trimmed_text = "\n".join(bounded_lines)
|
|
719
|
+
if len(trimmed_text) > max_chars:
|
|
720
|
+
trimmed_text = trimmed_text[:max_chars]
|
|
721
|
+
item.snippet = trimmed_text
|
|
722
|
+
item.estimated_tokens = min(rem_tokens, max(1, len(trimmed_text) // 4))
|
|
723
|
+
snippet_trimmed = True
|
|
724
|
+
return _can_fit(item, is_epistemic_uncertainty=is_epistemic_uncertainty)
|
|
725
|
+
|
|
726
|
+
def _accept(item: CandidateContextItem, is_coverage_fill: bool, why: list[str]) -> None:
|
|
727
|
+
nonlocal selected_tokens, selected_lines, selected_symbols_count
|
|
728
|
+
item.reason_code = item.infer_reason_code(is_coverage_fill=is_coverage_fill)
|
|
729
|
+
item.why_selected = [f"Reason: {item.reason_code}"] + why
|
|
730
|
+
selected.append(item)
|
|
731
|
+
selected_tokens += item.estimated_tokens
|
|
732
|
+
selected_lines += item.line_count
|
|
733
|
+
if not _is_epistemic_uncertainty(item):
|
|
734
|
+
selected_symbols_count += 1
|
|
735
|
+
selected_layers.add(item.coverage_layer)
|
|
736
|
+
selected_dimensions.add(item.infer_dimension())
|
|
737
|
+
selected_duplicate_keys.add(item.duplicate_key)
|
|
738
|
+
if item.file_path:
|
|
739
|
+
selected_files.add(item.file_path)
|
|
740
|
+
|
|
741
|
+
# Pass 0: Preserve high-value UNKNOWN / POSSIBLE / CONFLICT / AMBIGUOUS findings
|
|
742
|
+
for item in sorted_candidates:
|
|
743
|
+
if _is_excluded(item, task_spec):
|
|
744
|
+
continue
|
|
745
|
+
if _is_epistemic_uncertainty(item) and item.relevance_score >= 0.2:
|
|
746
|
+
if item.duplicate_key not in selected_duplicate_keys:
|
|
747
|
+
if _can_fit(item, is_epistemic_uncertainty=True) or _trim_to_fit(item, is_epistemic_uncertainty=True):
|
|
748
|
+
_accept(
|
|
749
|
+
item,
|
|
750
|
+
is_coverage_fill=True,
|
|
751
|
+
why=[
|
|
752
|
+
f"Epistemic preservation: '{item.epistemic_status}'",
|
|
753
|
+
f"Relevance: {item.relevance_score:.2f}",
|
|
754
|
+
],
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
# Pass 1a: Task Dimension Coverage pass — ensure required task dimensions
|
|
758
|
+
# (e.g. TARGET, CALLER, CALLEE, ROUTE, REGISTRATION, PROVIDER, TEST, PACKAGE)
|
|
759
|
+
# get represented before filling with repetitive candidates of the same dimension
|
|
760
|
+
for req_dim in required_dimensions:
|
|
761
|
+
if req_dim in selected_dimensions:
|
|
762
|
+
continue
|
|
763
|
+
for item in sorted_candidates:
|
|
764
|
+
if item in selected or _is_excluded(item, task_spec):
|
|
765
|
+
continue
|
|
766
|
+
if item.relevance_score < 0.2:
|
|
767
|
+
continue
|
|
768
|
+
if item.duplicate_key in selected_duplicate_keys:
|
|
769
|
+
continue
|
|
770
|
+
if item.infer_dimension() == req_dim:
|
|
771
|
+
if _can_fit(item) or (req_dim == "TARGET" and len(selected) == 0 and _trim_to_fit(item)):
|
|
772
|
+
_accept(
|
|
773
|
+
item,
|
|
774
|
+
is_coverage_fill=True,
|
|
775
|
+
why=[
|
|
776
|
+
f"Task dimension coverage guarantee: '{req_dim}'",
|
|
777
|
+
f"Relevance: {item.relevance_score:.2f}",
|
|
778
|
+
],
|
|
779
|
+
)
|
|
780
|
+
break
|
|
781
|
+
|
|
782
|
+
# Pass 1b: Task-Critical Candidates Priority pass — ensure all direct targets,
|
|
783
|
+
# routes, and direct callers/callees are admitted into the file budget before
|
|
784
|
+
# tangential layer coverage files consume file slots.
|
|
785
|
+
for item in sorted_candidates:
|
|
786
|
+
if item in selected or _is_excluded(item, task_spec):
|
|
787
|
+
continue
|
|
788
|
+
if item.duplicate_key in selected_duplicate_keys:
|
|
789
|
+
continue
|
|
790
|
+
dim = item.infer_dimension()
|
|
791
|
+
is_task_critical = (
|
|
792
|
+
dim in ("TARGET", "ROUTE", "CALLER")
|
|
793
|
+
or item.relevance_score >= 0.70
|
|
794
|
+
or (dim == "CALLEE" and item.relevance_score >= 0.50)
|
|
795
|
+
)
|
|
796
|
+
if is_task_critical:
|
|
797
|
+
if _can_fit(item) or (dim == "TARGET" and len(selected) == 0 and _trim_to_fit(item)):
|
|
798
|
+
_accept(
|
|
799
|
+
item,
|
|
800
|
+
is_coverage_fill=False,
|
|
801
|
+
why=[
|
|
802
|
+
f"Task-critical priority candidate: dimension '{dim}'",
|
|
803
|
+
f"Relevance: {item.relevance_score:.2f}, Evidence: {item.evidence_quality:.2f}",
|
|
804
|
+
],
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
# Pass 1c: Architectural Layer Coverage pass — ensure distinct layers
|
|
808
|
+
# (e.g. ENTRYPOINT, SERVICE, DATA, TEST, GIT) get represented if candidates exist
|
|
809
|
+
for item in sorted_candidates:
|
|
810
|
+
if item in selected or _is_excluded(item, task_spec):
|
|
811
|
+
continue
|
|
812
|
+
if item.relevance_score < 0.2:
|
|
813
|
+
continue
|
|
814
|
+
if item.duplicate_key in selected_duplicate_keys:
|
|
815
|
+
continue
|
|
816
|
+
if item.coverage_layer not in selected_layers and item.coverage_layer != "GENERAL":
|
|
817
|
+
if _can_fit(item):
|
|
818
|
+
_accept(
|
|
819
|
+
item,
|
|
820
|
+
is_coverage_fill=True,
|
|
821
|
+
why=[
|
|
822
|
+
f"Coverage layer guarantee: '{item.coverage_layer}'",
|
|
823
|
+
f"Relevance: {item.relevance_score:.2f}",
|
|
824
|
+
],
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
# Pass 2: Utility-driven knapsack pass for remaining budget
|
|
828
|
+
remaining_candidates = [c for c in sorted_candidates if c not in selected]
|
|
829
|
+
|
|
830
|
+
scored_pool: list[tuple[float, CandidateContextItem]] = []
|
|
831
|
+
for item in remaining_candidates:
|
|
832
|
+
u = compute_item_utility(item, selected_layers, selected_duplicate_keys, selected_dimensions)
|
|
833
|
+
scored_pool.append((u, item))
|
|
834
|
+
|
|
835
|
+
# Sort remaining pool deterministically by utility desc, item id asc
|
|
836
|
+
scored_pool.sort(
|
|
837
|
+
key=lambda pair: (
|
|
838
|
+
-pair[0],
|
|
839
|
+
-pair[1].relevance_score,
|
|
840
|
+
pair[1].package_distance,
|
|
841
|
+
pair[1].canonical_id or pair[1].file_path,
|
|
842
|
+
pair[1].start_line,
|
|
843
|
+
)
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
for utility, item in scored_pool:
|
|
847
|
+
if _is_excluded(item, task_spec):
|
|
848
|
+
continue
|
|
849
|
+
# Strict duplicate elimination by canonical identity
|
|
850
|
+
if item.duplicate_key in selected_duplicate_keys:
|
|
851
|
+
continue
|
|
852
|
+
# Minimum relevance threshold to avoid packing irrelevant items
|
|
853
|
+
if item.relevance_score < 0.35 or utility < 0.25:
|
|
854
|
+
continue
|
|
855
|
+
if _can_fit(item):
|
|
856
|
+
_accept(
|
|
857
|
+
item,
|
|
858
|
+
is_coverage_fill=False,
|
|
859
|
+
why=[
|
|
860
|
+
f"Knapsack utility score: {utility:.2f}",
|
|
861
|
+
f"Relevance: {item.relevance_score:.2f}, Evidence: {item.evidence_quality:.2f}, Freshness: {item.freshness}",
|
|
862
|
+
],
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
# Fallback Pass: If tight budget prevented any candidate from fitting, trim the top non-excluded candidate
|
|
866
|
+
if not selected:
|
|
867
|
+
for item in sorted_candidates:
|
|
868
|
+
if _is_excluded(item, task_spec):
|
|
869
|
+
continue
|
|
870
|
+
if _trim_to_fit(item):
|
|
871
|
+
_accept(
|
|
872
|
+
item,
|
|
873
|
+
is_coverage_fill=True,
|
|
874
|
+
why=[
|
|
875
|
+
"Primary target preserved under tight budget via bounded snippet truncation",
|
|
876
|
+
f"Relevance: {item.relevance_score:.2f}",
|
|
877
|
+
],
|
|
878
|
+
)
|
|
879
|
+
break
|
|
880
|
+
|
|
881
|
+
# Final deterministic ordering for presentation:
|
|
882
|
+
# Coverage layers first (ENTRYPOINT -> HANDLER -> SERVICE -> DATA -> TEST -> GIT -> ARCHITECTURE -> GENERAL),
|
|
883
|
+
# then relevance descending, file path, and line number
|
|
884
|
+
_LAYER_ORDER = {
|
|
885
|
+
"ENTRYPOINT": 0,
|
|
886
|
+
"HANDLER": 1,
|
|
887
|
+
"SERVICE": 2,
|
|
888
|
+
"DATA": 3,
|
|
889
|
+
"TEST": 4,
|
|
890
|
+
"GIT": 5,
|
|
891
|
+
"ARCHITECTURE": 6,
|
|
892
|
+
"GENERAL": 7,
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
selected.sort(
|
|
896
|
+
key=lambda x: (
|
|
897
|
+
_LAYER_ORDER.get(x.coverage_layer, 9),
|
|
898
|
+
-x.relevance_score,
|
|
899
|
+
x.file_path,
|
|
900
|
+
x.start_line,
|
|
901
|
+
)
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
token_reduction = (
|
|
905
|
+
1.0 - (selected_tokens / max(candidate_tokens_total, 1))
|
|
906
|
+
if candidate_tokens_total > 0
|
|
907
|
+
else 0.0
|
|
908
|
+
)
|
|
909
|
+
cand_reduction = (
|
|
910
|
+
1.0 - (len(selected) / max(candidate_count_total, 1))
|
|
911
|
+
if candidate_count_total > 0
|
|
912
|
+
else 0.0
|
|
913
|
+
)
|
|
914
|
+
utilization = min(1.0, selected_tokens / max(token_budget, 1))
|
|
915
|
+
|
|
916
|
+
# Coverage score over available required dimensions
|
|
917
|
+
available_dims = {c.infer_dimension() for c in candidates if not _is_excluded(c, task_spec)}
|
|
918
|
+
effective_req_dims = [d for d in required_dimensions if d in available_dims] or list(required_dimensions)
|
|
919
|
+
covered_dims = tuple(d for d in required_dimensions if d in selected_dimensions)
|
|
920
|
+
missing_dims = tuple(d for d in required_dimensions if d not in selected_dimensions)
|
|
921
|
+
if effective_req_dims:
|
|
922
|
+
cov_score = sum(1 for d in effective_req_dims if d in selected_dimensions) / len(effective_req_dims)
|
|
923
|
+
else:
|
|
924
|
+
cov_score = 1.0 if selected else 0.0
|
|
925
|
+
|
|
926
|
+
# Redundancy ratio in selected items (should be 0.0 due to canonical deduplication)
|
|
927
|
+
seen_sel_keys: set[str] = set()
|
|
928
|
+
dup_sel = 0
|
|
929
|
+
for s in selected:
|
|
930
|
+
if s.duplicate_key in seen_sel_keys:
|
|
931
|
+
dup_sel += 1
|
|
932
|
+
else:
|
|
933
|
+
seen_sel_keys.add(s.duplicate_key)
|
|
934
|
+
redundancy_ratio = dup_sel / max(len(selected), 1)
|
|
935
|
+
|
|
936
|
+
# Evidence density: verified claims (selected symbols + verified relationships) per 100 selected tokens
|
|
937
|
+
total_claims = len(selected) + max(0, verified_claims_count)
|
|
938
|
+
evidence_density = (total_claims / max(selected_tokens / 100.0, 1.0)) if selected_tokens > 0 else 0.0
|
|
939
|
+
|
|
940
|
+
rejections_list: list[dict[str, object]] = []
|
|
941
|
+
selected_ids = {s.item_id for s in selected}
|
|
942
|
+
budget_rejected_count = 0
|
|
943
|
+
for item in candidates:
|
|
944
|
+
if item.item_id not in selected_ids:
|
|
945
|
+
if _is_excluded(item, task_spec):
|
|
946
|
+
reason = "Hard exclusion constraint"
|
|
947
|
+
elif item.duplicate_key in selected_duplicate_keys:
|
|
948
|
+
reason = f"Duplicate of selected symbol/chunk ({item.duplicate_key})"
|
|
949
|
+
elif item.graph_distance > max_depth:
|
|
950
|
+
reason = f"Graph depth limit reached ({max_depth} hops)"
|
|
951
|
+
budget_rejected_count += 1
|
|
952
|
+
elif item.package_distance != 99 and item.package_distance > max_package_depth:
|
|
953
|
+
reason = f"Package depth limit reached ({max_package_depth} hops)"
|
|
954
|
+
budget_rejected_count += 1
|
|
955
|
+
elif selected_symbols_count >= max_symbols:
|
|
956
|
+
reason = f"Symbol budget limit reached ({max_symbols} symbols)"
|
|
957
|
+
budget_rejected_count += 1
|
|
958
|
+
elif item.file_path not in selected_files and len(selected_files) >= max_files:
|
|
959
|
+
reason = f"File budget limit reached ({max_files} files)"
|
|
960
|
+
budget_rejected_count += 1
|
|
961
|
+
elif selected_lines + item.line_count > max_lines:
|
|
962
|
+
reason = f"Line budget limit reached ({max_lines} lines)"
|
|
963
|
+
budget_rejected_count += 1
|
|
964
|
+
elif item.estimated_tokens + selected_tokens > token_budget:
|
|
965
|
+
reason = f"Token budget limit reached ({token_budget} tokens)"
|
|
966
|
+
budget_rejected_count += 1
|
|
967
|
+
elif item.freshness == "STALE":
|
|
968
|
+
reason = "Stale source hash on disk"
|
|
969
|
+
else:
|
|
970
|
+
reason = f"Lower utility/relevance ({item.relevance_score:.2f}) compared to selected candidates"
|
|
971
|
+
rejections_list.append(
|
|
972
|
+
{
|
|
973
|
+
"item_id": item.item_id,
|
|
974
|
+
"file": item.file_path,
|
|
975
|
+
"start_line": item.start_line,
|
|
976
|
+
"symbol": item.canonical_id or item.data.get("symbol"),
|
|
977
|
+
"reason": reason,
|
|
978
|
+
"relevance": item.relevance_score,
|
|
979
|
+
}
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
is_truncated = bool(was_clamped or snippet_trimmed or budget_rejected_count > 0)
|
|
983
|
+
|
|
984
|
+
budget = ContextBudget(
|
|
985
|
+
candidate_tokens=candidate_tokens_total,
|
|
986
|
+
selected_tokens=selected_tokens,
|
|
987
|
+
reduction_ratio=round(token_reduction, 4),
|
|
988
|
+
budget_limit=token_budget,
|
|
989
|
+
rejections=tuple(rejections_list[:50]),
|
|
990
|
+
candidate_count=candidate_count_total,
|
|
991
|
+
selected_count=len(selected),
|
|
992
|
+
candidate_reduction_ratio=round(cand_reduction, 4),
|
|
993
|
+
selected_reduction_ratio=round(token_reduction, 4),
|
|
994
|
+
budget_utilization=round(utilization, 4),
|
|
995
|
+
coverage_score=round(cov_score, 4),
|
|
996
|
+
redundancy_ratio=round(redundancy_ratio, 4),
|
|
997
|
+
evidence_density=round(evidence_density, 4),
|
|
998
|
+
direct_file_reads=len(selected_files),
|
|
999
|
+
direct_file_lines=selected_lines,
|
|
1000
|
+
selected_files=len(selected_files),
|
|
1001
|
+
selected_lines=selected_lines,
|
|
1002
|
+
truncated=is_truncated,
|
|
1003
|
+
tool_calls=tool_calls,
|
|
1004
|
+
max_symbols=max_symbols,
|
|
1005
|
+
max_relationships=max_relationships,
|
|
1006
|
+
max_files=max_files,
|
|
1007
|
+
max_lines=max_lines,
|
|
1008
|
+
max_depth=max_depth,
|
|
1009
|
+
max_package_depth=max_package_depth,
|
|
1010
|
+
covered_dimensions=covered_dims,
|
|
1011
|
+
missing_dimensions=missing_dims,
|
|
1012
|
+
)
|
|
1013
|
+
|
|
1014
|
+
return selected, budget
|