sourcecode 2.3.0__py3-none-any.whl → 2.5.0__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.
@@ -407,7 +407,7 @@ class ContextGraph:
407
407
  __slots__ = (
408
408
  "_cir", "_nodes_by_fqn", "_nodes", "_build_ms",
409
409
  "_body_index", "_literal_index", "_guard_index", "_span_index",
410
- "_class_type_index",
410
+ "_class_type_index", "_relations_all", "_symbols_sorted",
411
411
  )
412
412
 
413
413
  def __init__(self, cir: CanonicalRepositoryIR, *, build_ms: float = 0.0) -> None:
@@ -431,6 +431,14 @@ class ContextGraph:
431
431
  _node_to_symbol(n) for n in raw_nodes
432
432
  )
433
433
  self._nodes_by_fqn: dict[str, Symbol] = {s.fqn: s for s in self._nodes}
434
+ # Memoized full projections (the CIR is immutable for this graph's lifetime).
435
+ # `relations()`/`symbols()` are called once PER FILE by per-file consumers such as
436
+ # `_java_contract_from_graph`; without these caches each call re-converted every
437
+ # edge (`_edge_to_relation`) and re-sorted every node — an O(files × edges) blow-up
438
+ # that made cold `ask` / `ask --agent` take minutes on large repos. Built once,
439
+ # filtered cheaply thereafter.
440
+ self._relations_all: Optional[tuple[Relation, ...]] = None
441
+ self._symbols_sorted: Optional[tuple[Symbol, ...]] = None
434
442
 
435
443
  # -- construction -------------------------------------------------------
436
444
 
@@ -497,19 +505,18 @@ class ContextGraph:
497
505
  set. Results are sorted by FQN for determinism. This is the primitive
498
506
  the role-convenience methods below are thin sugar over.
499
507
  """
500
- out: list[Symbol] = []
501
- for s in self._nodes:
502
- if kind is not None and s.kind != kind:
503
- continue
504
- if role is not None and s.role != role:
505
- continue
506
- if annotated_with is not None and not s.has_annotation(annotated_with):
507
- continue
508
- if name_contains is not None and name_contains not in s.fqn:
509
- continue
510
- out.append(s)
511
- out.sort(key=lambda s: s.fqn)
512
- return out
508
+ if self._symbols_sorted is None:
509
+ self._symbols_sorted = tuple(sorted(self._nodes, key=lambda s: s.fqn))
510
+ # Filter over the memoized fqn-sorted set (filtering preserves that order, so the
511
+ # result is identical to sorting after filtering — but the sort happens once).
512
+ return [
513
+ s
514
+ for s in self._symbols_sorted
515
+ if (kind is None or s.kind == kind)
516
+ and (role is None or s.role == role)
517
+ and (annotated_with is None or s.has_annotation(annotated_with))
518
+ and (name_contains is None or name_contains in s.fqn)
519
+ ]
513
520
 
514
521
  def types(self) -> list[Symbol]:
515
522
  """All class/interface/enum/annotation symbols, sorted by FQN."""
@@ -575,16 +582,17 @@ class ContextGraph:
575
582
  ) -> list[Relation]:
576
583
  """All relations matching the given filters (AND-combined), in the IR's
577
584
  stable edge order (already sorted from → type → to)."""
578
- out: list[Relation] = []
579
- for e in self._cir.call_graph:
580
- if kind is not None and e.get("type") != kind:
581
- continue
582
- if source is not None and e.get("from") != source:
583
- continue
584
- if target is not None and e.get("to") != target:
585
- continue
586
- out.append(_edge_to_relation(e))
587
- return out
585
+ if self._relations_all is None:
586
+ # Convert every edge ONCE; filtered calls then scan the cached Relation
587
+ # objects without re-running `_edge_to_relation` (the cold-path hotspot).
588
+ self._relations_all = tuple(_edge_to_relation(e) for e in self._cir.call_graph)
589
+ return [
590
+ r
591
+ for r in self._relations_all
592
+ if (kind is None or r.kind == kind)
593
+ and (source is None or r.source == source)
594
+ and (target is None or r.target == target)
595
+ ]
588
596
 
589
597
  # -- inheritance / implementation (reuses cir.implementation_graph) ------
590
598
 
sourcecode/explain.py CHANGED
@@ -133,6 +133,30 @@ class ClassExplanation:
133
133
  "found": self.found,
134
134
  }
135
135
 
136
+ @classmethod
137
+ def from_dict(cls, d: dict) -> "ClassExplanation":
138
+ """Reconstruct from :meth:`to_dict` output (lossless round-trip).
139
+
140
+ The informational ``incoming_callers_note`` key is derived, not stored
141
+ on the instance, so it is dropped on the way back in.
142
+ """
143
+ return cls(
144
+ class_name=d.get("class_name", ""),
145
+ class_fqn=d.get("class_fqn", ""),
146
+ stereotype=d.get("stereotype", ""),
147
+ purpose=d.get("purpose", ""),
148
+ public_methods=list(d.get("public_methods", [])),
149
+ incoming_callers=list(d.get("incoming_callers", [])),
150
+ outgoing_deps=list(d.get("outgoing_deps", [])),
151
+ events_published=list(d.get("events_published", [])),
152
+ events_consumed=list(d.get("events_consumed", [])),
153
+ transactions=list(d.get("transactions", [])),
154
+ security_constraints=list(d.get("security_constraints", [])),
155
+ rest_endpoints=list(d.get("rest_endpoints", [])),
156
+ warnings=list(d.get("warnings", [])),
157
+ found=bool(d.get("found", True)),
158
+ )
159
+
136
160
 
137
161
  # ---------------------------------------------------------------------------
138
162
  # FQN resolution
@@ -742,7 +742,7 @@ def _extract_symbols(
742
742
  """Phase 1: Extract symbols from a Java source file.
743
743
 
744
744
  extra_capture: extra annotation tokens (e.g. custom security annotations like
745
- "@M3FiltroSeguridad") whose argument lists must be stored in annotation_values
745
+ "@CustomSecurityAnnotation") whose argument lists must be stored in annotation_values
746
746
  even though they are not in the built-in _CAPTURE_ANN_ARGS set.
747
747
 
748
748
  Returns (package, symbols, raw_imports).
@@ -0,0 +1,192 @@
1
+ """Semantic Retrieval Engine — Layer 3.5 (ADR-0001 / DESIGN-semantic-retrieval.md).
2
+
3
+ Turns "ask a typed question of the repository's knowledge" into a first-class,
4
+ reusable operation. This layer **queries already-built structured knowledge**; it
5
+ does NOT extract, does NOT analyze source, does NOT build the Semantic IR or the
6
+ ContextGraph, and does NOT compute new Evidence. Every answer is a *composition* of
7
+ existing Knowledge-Layer capabilities (SemanticServices, ContextGraph, the derived
8
+ engines, the Evidence Providers) acquired through the Knowledge Artifact Store.
9
+
10
+ Public pipeline (design §3.1):
11
+
12
+ RetrievalRequest → IntentResolver → QueryPlanner → KnowledgeQuery
13
+ → KnowledgeExecutor → KnowledgeResult (+ RetrievalEvidence)
14
+
15
+ Phase 2 delivers the complete skeleton plus exactly one intent — ARCHITECTURAL_CORE —
16
+ proven end-to-end over the existing ArchetypeClassifier / GraphEvidenceProvider. Adding
17
+ a further intent is: (1) a new `RetrievalIntent` member, (2) a plan in `QueryPlanner`,
18
+ (3) a (usually trivial) resolver strategy — the executor is unchanged when the plan
19
+ composes existing steps.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ from sourcecode.retrieval.context import KnowledgeContext
24
+ from sourcecode.retrieval.errors import (
25
+ IncompletePlanError,
26
+ KnowledgeUnavailableError,
27
+ RetrievalError,
28
+ StepContractError,
29
+ StepExecutionError,
30
+ UnknownDimensionError,
31
+ UnknownIntentError,
32
+ UnknownStepError,
33
+ )
34
+ from sourcecode.retrieval.executor import KnowledgeExecutor
35
+ from sourcecode.retrieval.planner import QueryPlanner
36
+ from sourcecode.retrieval.query import (
37
+ BuildResult,
38
+ Characterize,
39
+ CollectEvidence,
40
+ CollectImpactEvidence,
41
+ ComputeBlastRadius,
42
+ KnowledgeQuery,
43
+ RankByRisk,
44
+ RankByScore,
45
+ ResolveDependents,
46
+ ResolveEndpoints,
47
+ ResolveIntegrations,
48
+ ResolveModules,
49
+ ResolveTarget,
50
+ ResolveTransactions,
51
+ ResolveTransactionFlow,
52
+ ResolveTransactionEntrypoints,
53
+ ResolvePropagation,
54
+ ResolveSecuritySurface,
55
+ ResolveSecurityChain,
56
+ ResolveValidationPoints,
57
+ RankByCriticality,
58
+ CollectSecurityEvidence,
59
+ ResolveEndpointInventory,
60
+ ResolveIntegrationInventory,
61
+ ResolveEventTopology,
62
+ CollectInterfaceEvidence,
63
+ ResolveScope,
64
+ ResolveSubsystemInventory,
65
+ ResolveSubsystemMembers,
66
+ ResolveModuleDependents,
67
+ ResolveModuleDependencies,
68
+ ResolveTypeDependents,
69
+ CollectStructureEvidence,
70
+ ResolveEndpointRef,
71
+ ResolveEndpointContract,
72
+ ResolveEndpointSecurity,
73
+ ResolveCallNeighborhood,
74
+ CollectEndpointEvidence,
75
+ ResolveExecutionPaths,
76
+ ResolveTransactionsReaching,
77
+ ResolveSubsystemCoupling,
78
+ CollectGraphEvidence,
79
+ Step,
80
+ step_from_dict,
81
+ )
82
+ from sourcecode.retrieval.runtime import (
83
+ QueryPlanRuntime,
84
+ RuntimeReport,
85
+ RuntimeResult,
86
+ StepContext,
87
+ StepRegistry,
88
+ StepTrace,
89
+ )
90
+ from sourcecode.retrieval.steps import default_registry
91
+ from sourcecode.retrieval.request import (
92
+ RetrievalAnchor,
93
+ RetrievalIntent,
94
+ RetrievalOptions,
95
+ RetrievalRequest,
96
+ )
97
+ from sourcecode.retrieval.resolution import IntentResolver, ResolvedAnchor, ResolvedIntent
98
+ from sourcecode.retrieval.result import (
99
+ Explanation,
100
+ KnowledgeEntity,
101
+ KnowledgeResult,
102
+ Observation,
103
+ Provenance,
104
+ RetrievalEvidence,
105
+ )
106
+ from sourcecode.retrieval.retriever import SemanticRetriever
107
+
108
+ __all__ = [
109
+ # façade
110
+ "SemanticRetriever",
111
+ # request
112
+ "RetrievalRequest",
113
+ "RetrievalIntent",
114
+ "RetrievalAnchor",
115
+ "RetrievalOptions",
116
+ # resolution
117
+ "IntentResolver",
118
+ "ResolvedIntent",
119
+ "ResolvedAnchor",
120
+ # plan
121
+ "QueryPlanner",
122
+ "KnowledgeQuery",
123
+ "Step",
124
+ "Characterize",
125
+ "RankByScore",
126
+ "CollectEvidence",
127
+ "BuildResult",
128
+ "ResolveTarget",
129
+ "ComputeBlastRadius",
130
+ "ResolveDependents",
131
+ "ResolveEndpoints",
132
+ "ResolveIntegrations",
133
+ "ResolveTransactions",
134
+ "ResolveModules",
135
+ "RankByRisk",
136
+ "CollectImpactEvidence",
137
+ "ResolveTransactionFlow",
138
+ "ResolveTransactionEntrypoints",
139
+ "ResolvePropagation",
140
+ "ResolveSecuritySurface",
141
+ "ResolveSecurityChain",
142
+ "ResolveValidationPoints",
143
+ "RankByCriticality",
144
+ "CollectSecurityEvidence",
145
+ "ResolveEndpointInventory",
146
+ "ResolveIntegrationInventory",
147
+ "ResolveEventTopology",
148
+ "CollectInterfaceEvidence",
149
+ "ResolveScope",
150
+ "ResolveSubsystemInventory",
151
+ "ResolveSubsystemMembers",
152
+ "ResolveModuleDependents",
153
+ "ResolveModuleDependencies",
154
+ "ResolveTypeDependents",
155
+ "CollectStructureEvidence",
156
+ "ResolveEndpointRef",
157
+ "ResolveEndpointContract",
158
+ "ResolveEndpointSecurity",
159
+ "ResolveCallNeighborhood",
160
+ "CollectEndpointEvidence",
161
+ "ResolveExecutionPaths",
162
+ "ResolveTransactionsReaching",
163
+ "ResolveSubsystemCoupling",
164
+ "CollectGraphEvidence",
165
+ # execution / runtime
166
+ "KnowledgeExecutor",
167
+ "KnowledgeContext",
168
+ "QueryPlanRuntime",
169
+ "StepRegistry",
170
+ "StepContext",
171
+ "StepTrace",
172
+ "RuntimeReport",
173
+ "RuntimeResult",
174
+ "default_registry",
175
+ "step_from_dict",
176
+ # result
177
+ "KnowledgeResult",
178
+ "KnowledgeEntity",
179
+ "RetrievalEvidence",
180
+ "Observation",
181
+ "Explanation",
182
+ "Provenance",
183
+ # errors
184
+ "RetrievalError",
185
+ "UnknownIntentError",
186
+ "UnknownStepError",
187
+ "UnknownDimensionError",
188
+ "IncompletePlanError",
189
+ "KnowledgeUnavailableError",
190
+ "StepExecutionError",
191
+ "StepContractError",
192
+ ]
@@ -0,0 +1,241 @@
1
+ """KnowledgeContext — the read-only bundle of ALREADY-BUILT knowledge (design §3.2, §4.1).
2
+
3
+ The retrieval layer never extracts. It receives knowledge here and only queries it.
4
+ Two ways to obtain a context:
5
+
6
+ * **Injected** — construct with pre-built handles (a `SourceMap`, a `ContextGraph`).
7
+ This is how the pipeline is unit-tested: no scan, no parse, provably no source access.
8
+ * **Acquired** — `KnowledgeContext.for_repo(root)` obtains existing knowledge using the
9
+ engine's OWN builders (the same AdaptiveScanner/ProjectDetector/GraphAnalyzer path the
10
+ `archetype` command uses, and the `context_cache` Knowledge Artifact Store for the
11
+ CIR). This is the acquisition BOUNDARY — it delegates to existing extractors; it adds
12
+ no retrieval-specific parser. The pipeline stages (resolver/planner/executor) never
13
+ call acquisition; they only read the handles exposed here.
14
+
15
+ `ContextGraph` / `SemanticServices` are acquired **lazily** via the Knowledge Artifact
16
+ Store so an intent that does not need them (like ARCHITECTURAL_CORE) pays nothing, while
17
+ the wiring is present and exercised for future anchored intents.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ from pathlib import Path
22
+ from time import perf_counter
23
+ from typing import TYPE_CHECKING, Any, Optional
24
+
25
+ from sourcecode.retrieval.errors import KnowledgeUnavailableError
26
+
27
+ if TYPE_CHECKING:
28
+ from sourcecode.context_graph import ContextGraph
29
+ from sourcecode.schema import SourceMap
30
+ from sourcecode.semantic_services import SemanticServices
31
+
32
+
33
+ class KnowledgeContext:
34
+ """Handles to already-built knowledge. Read-only from the pipeline's view."""
35
+
36
+ def __init__(
37
+ self,
38
+ *,
39
+ root: Optional[Path] = None,
40
+ repo_root: Optional[Path] = None,
41
+ source_map: "Optional[SourceMap]" = None,
42
+ module_graph: Any = None,
43
+ context_graph: "Optional[ContextGraph]" = None,
44
+ file_list: Optional[list[str]] = None,
45
+ repo_ir: Optional[dict] = None,
46
+ spring_model: object = None,
47
+ ) -> None:
48
+ self._root = root
49
+ self._repo_root = repo_root or root
50
+ self._source_map = source_map
51
+ self._module_graph = module_graph
52
+ # True once the SourceMap/module_graph pair has been acquired (or injected), so a
53
+ # legitimately-None module_graph is not re-acquired on every access.
54
+ self._source_map_ready = source_map is not None
55
+ self._context_graph = context_graph
56
+ self._file_list = file_list
57
+ self._repo_ir = repo_ir
58
+ self._spring_model = spring_model
59
+ self._services: "Optional[SemanticServices]" = None
60
+ self._cache_render: Optional[str] = None
61
+ # B2: acquisition instrumentation — ordered (name, wall-time-ms) records of every
62
+ # lazy knowledge acquisition this context performed. Cross-cutting infra: it lets
63
+ # the runtime report attribute the real cost of acquiring knowledge separately from
64
+ # the step that happened to trigger it, without the runtime knowing any intent.
65
+ self._acquisitions: list[tuple[str, float]] = []
66
+
67
+ # ── read handles ────────────────────────────────────────────────────────────
68
+ @property
69
+ def root(self) -> Optional[Path]:
70
+ return self._root
71
+
72
+ @property
73
+ def source_map(self) -> "SourceMap":
74
+ """The built `SourceMap`, acquired lazily from the same AdaptiveScanner /
75
+ ProjectDetector / GraphAnalyzer path the `archetype` command uses. Acquired only
76
+ when an intent actually needs it (e.g. ARCHITECTURAL_*); tx / security / impact
77
+ intents never touch it and pay nothing for its ~7s construction (design B1)."""
78
+ if not self._source_map_ready:
79
+ self._acquire_source_map()
80
+ if self._source_map is None:
81
+ raise KnowledgeUnavailableError("no SourceMap was injected or acquired")
82
+ return self._source_map
83
+
84
+ @property
85
+ def module_graph(self) -> Any:
86
+ if not self._source_map_ready:
87
+ self._acquire_source_map()
88
+ return self._module_graph
89
+
90
+ @property
91
+ def context_graph(self) -> "ContextGraph":
92
+ """The built ContextGraph, fetched lazily from the Knowledge Artifact Store
93
+ (cached CIR — no re-parse on a hit). Never built by the retrieval steps."""
94
+ if self._context_graph is None:
95
+ self._acquire_context_graph()
96
+ assert self._context_graph is not None
97
+ return self._context_graph
98
+
99
+ @property
100
+ def services(self) -> "SemanticServices":
101
+ if self._services is None:
102
+ from sourcecode.semantic_services import SemanticServices
103
+
104
+ t0 = perf_counter()
105
+ self._services = SemanticServices(self.context_graph)
106
+ self._record_acquisition("services", t0)
107
+ return self._services
108
+
109
+ @property
110
+ def repo_ir(self) -> dict:
111
+ """The structural repo IR dict (from `build_repo_ir`), acquired lazily and shared
112
+ across steps. Same builder the legacy `impact` command uses — the retrieval layer
113
+ introduces no parser; it reuses this one. Built at most once per context."""
114
+ if self._repo_ir is None:
115
+ self._acquire_repo_ir()
116
+ assert self._repo_ir is not None
117
+ return self._repo_ir
118
+
119
+ @property
120
+ def spring_model(self) -> object:
121
+ """The `SpringSemanticModel` (tx index, endpoint index, call adjacency, bean/event
122
+ graphs), built lazily over the acquired ContextGraph's CIR — the SAME builder the
123
+ `spring-audit` command uses. Reused, never re-derived. Built at most once."""
124
+ if self._spring_model is None:
125
+ from sourcecode.spring_model import SpringSemanticModel
126
+
127
+ t0 = perf_counter()
128
+ self._spring_model = SpringSemanticModel.build(self.context_graph.cir)
129
+ self._record_acquisition("spring_model", t0)
130
+ return self._spring_model
131
+
132
+ @property
133
+ def cache_render(self) -> str:
134
+ """Human-free provenance of how the CIR was obtained (cache HIT/MISS/disabled),
135
+ or "not acquired" when the ContextGraph was never needed."""
136
+ return self._cache_render or "not acquired"
137
+
138
+ @property
139
+ def acquisitions(self) -> tuple[tuple[str, float], ...]:
140
+ """Ordered (name, wall-time-ms) records of the knowledge acquisitions performed
141
+ (B2). Empty when everything was injected (no acquisition happened)."""
142
+ return tuple(self._acquisitions)
143
+
144
+ def _record_acquisition(self, name: str, t0: float) -> None:
145
+ self._acquisitions.append((name, (perf_counter() - t0) * 1000.0))
146
+
147
+ # ── lazy ContextGraph acquisition (Knowledge Artifact Store) ────────────────
148
+ def _acquire_context_graph(self) -> None:
149
+ if self._root is None:
150
+ raise KnowledgeUnavailableError(
151
+ "cannot acquire a ContextGraph without a repository root"
152
+ )
153
+ from sourcecode.context_cache import get_or_build_cir
154
+ from sourcecode.context_graph import ContextGraph
155
+ from sourcecode.repository_ir import find_java_files
156
+
157
+ t0 = perf_counter()
158
+ files = self._file_list if self._file_list is not None else find_java_files(self._root)
159
+ cir, lookup = get_or_build_cir(self._repo_root or self._root, self._root, files)
160
+ self._context_graph = ContextGraph.from_cir(cir)
161
+ self._cache_render = lookup.render()
162
+ self._record_acquisition("context_graph", t0)
163
+
164
+ def _acquire_source_map(self) -> None:
165
+ if self._root is None:
166
+ raise KnowledgeUnavailableError(
167
+ "cannot acquire a SourceMap without a repository root"
168
+ )
169
+ t0 = perf_counter()
170
+ self._source_map, self._module_graph = _acquire_source_map(self._root)
171
+ self._source_map_ready = True
172
+ self._record_acquisition("source_map", t0)
173
+
174
+ def _acquire_repo_ir(self) -> None:
175
+ if self._root is None:
176
+ raise KnowledgeUnavailableError("cannot acquire a repo IR without a repository root")
177
+ from sourcecode.repository_ir import build_repo_ir, find_java_files
178
+
179
+ t0 = perf_counter()
180
+ files = self._file_list if self._file_list is not None else find_java_files(self._root)
181
+ self._repo_ir = build_repo_ir(files, self._root)
182
+ self._record_acquisition("repo_ir", t0)
183
+
184
+ # ── acquisition boundary ────────────────────────────────────────────────────
185
+ @classmethod
186
+ def for_repo(cls, root: Path) -> "KnowledgeContext":
187
+ """Obtain existing knowledge for a repository via the engine's own builders.
188
+
189
+ This mirrors the `archetype` command's SourceMap construction exactly — it
190
+ reuses AdaptiveScanner / ProjectDetector / GraphAnalyzer; it does not introduce
191
+ a retrieval-specific parser. Both the SourceMap and the ContextGraph are left for
192
+ lazy acquisition — built only if an intent actually needs them (design B1).
193
+ """
194
+ target = Path(root).resolve()
195
+ return cls(root=target, repo_root=target)
196
+
197
+
198
+ def _acquire_source_map(target: Path) -> "tuple[SourceMap, Any]":
199
+ """Build a SourceMap + module graph using the existing analyzers (acquisition
200
+ boundary). Best-effort on the module graph, exactly like the `archetype` command:
201
+ on any failure it degrades to name-mass-only (module_graph=None)."""
202
+ from sourcecode.adaptive_scanner import AdaptiveScanner
203
+ from sourcecode.detectors import ProjectDetector, build_default_detectors
204
+ from sourcecode.repo_classifier import RepoClassifier
205
+ from sourcecode.schema import SourceMap
206
+ from sourcecode.tree_utils import flatten_file_tree
207
+
208
+ topology = RepoClassifier().classify(target)
209
+ scanner = AdaptiveScanner(target, topology=topology, base_depth=12)
210
+ file_tree = scanner.scan_tree()
211
+ manifests = scanner.find_manifests()
212
+
213
+ detector = ProjectDetector(build_default_detectors())
214
+ stacks, entry_points, _ = detector.detect(target, file_tree, manifests)
215
+ stacks, project_type = detector.classify_results(file_tree, stacks, entry_points)
216
+
217
+ sm = SourceMap(
218
+ file_tree=file_tree,
219
+ stacks=stacks,
220
+ project_type=project_type,
221
+ entry_points=entry_points,
222
+ )
223
+ sm.file_paths = [p.replace("\\", "/") for p in flatten_file_tree(file_tree)]
224
+ java = next((s for s in stacks if s.stack == "java"), None)
225
+ if java is not None:
226
+ sm.packaging = getattr(java, "packaging", None)
227
+
228
+ module_graph = None
229
+ try:
230
+ from sourcecode.graph_analyzer import GraphAnalyzer
231
+
232
+ module_graph = GraphAnalyzer(max_nodes=1200, max_edges=4000).analyze(
233
+ target, file_tree, detail="full", entry_points=entry_points,
234
+ )
235
+ if module_graph is not None:
236
+ sm.module_graph = module_graph
237
+ sm.module_graph_summary = module_graph.summary
238
+ except Exception:
239
+ module_graph = None
240
+
241
+ return sm, module_graph
@@ -0,0 +1,41 @@
1
+ """Typed errors for the Semantic Retrieval Engine.
2
+
3
+ All are programming/wiring faults (an unknown intent, a plan the executor cannot
4
+ interpret) — never a "no result" signal. Absence of knowledge is expressed in the
5
+ typed KnowledgeResult (empty entities, `signals_missing`), not by raising.
6
+ """
7
+ from __future__ import annotations
8
+
9
+
10
+ class RetrievalError(Exception):
11
+ """Base class for every retrieval-layer fault."""
12
+
13
+
14
+ class UnknownIntentError(RetrievalError):
15
+ """No resolver/planner strategy is registered for the requested intent."""
16
+
17
+
18
+ class UnknownStepError(RetrievalError):
19
+ """The executor has no handler for a step type in the plan (plan/executor drift)."""
20
+
21
+
22
+ class UnknownDimensionError(RetrievalError):
23
+ """A Characterize step named an archetype dimension the engine does not produce."""
24
+
25
+
26
+ class IncompletePlanError(RetrievalError):
27
+ """A plan finished without producing a KnowledgeResult (missing BuildResult step)."""
28
+
29
+
30
+ class KnowledgeUnavailableError(RetrievalError):
31
+ """A required knowledge handle was neither injected nor acquirable."""
32
+
33
+
34
+ class StepExecutionError(RetrievalError):
35
+ """A step handler raised an unexpected (non-retrieval) error while running. Wraps
36
+ the original cause; the failing step is recorded in the runtime trace."""
37
+
38
+
39
+ class StepContractError(RetrievalError):
40
+ """A step required a shared-context slot that a prior step did not produce (a plan
41
+ composed steps in an invalid order)."""
@@ -0,0 +1,51 @@
1
+ """KnowledgeExecutor — thin façade over the Query Plan Runtime (design §4.5).
2
+
3
+ Fase 4 moved all execution logic into `QueryPlanRuntime` + the step handlers in
4
+ `steps.py`. This façade preserves the stable public surface: `execute()` returns just the
5
+ typed `KnowledgeResult` (as consumers and Phase-2 tests expect), while `run()` exposes the
6
+ full `RuntimeResult` (result + execution report with per-step trace and profiling).
7
+
8
+ The executor owns no execution logic — it delegates to the Runtime. That is why adding an
9
+ intent never touches this file.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from typing import Optional
14
+
15
+ from sourcecode.retrieval.context import KnowledgeContext
16
+ from sourcecode.retrieval.query import KnowledgeQuery
17
+ from sourcecode.retrieval.request import RetrievalOptions
18
+ from sourcecode.retrieval.resolution import ResolvedIntent
19
+ from sourcecode.retrieval.result import KnowledgeResult
20
+ from sourcecode.retrieval.runtime import QueryPlanRuntime, RuntimeResult
21
+
22
+
23
+ class KnowledgeExecutor:
24
+ """Runs a `KnowledgeQuery` over a `KnowledgeContext` via the Query Plan Runtime."""
25
+
26
+ def __init__(self, runtime: Optional[QueryPlanRuntime] = None) -> None:
27
+ self._runtime = runtime or QueryPlanRuntime()
28
+
29
+ @property
30
+ def runtime(self) -> QueryPlanRuntime:
31
+ return self._runtime
32
+
33
+ def run(
34
+ self,
35
+ query: KnowledgeQuery,
36
+ ctx: KnowledgeContext,
37
+ resolved: Optional[ResolvedIntent] = None,
38
+ options: Optional[RetrievalOptions] = None,
39
+ ) -> RuntimeResult:
40
+ """Execute and return the full runtime result (typed result + execution report)."""
41
+ return self._runtime.run(query, ctx, resolved, options)
42
+
43
+ def execute(
44
+ self,
45
+ query: KnowledgeQuery,
46
+ ctx: KnowledgeContext,
47
+ resolved: Optional[ResolvedIntent] = None,
48
+ options: Optional[RetrievalOptions] = None,
49
+ ) -> KnowledgeResult:
50
+ """Execute and return only the typed `KnowledgeResult`."""
51
+ return self._runtime.run(query, ctx, resolved, options).result