codecortex-context-engine 0.1.0a1__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 (90) hide show
  1. codecortex/__init__.py +3 -0
  2. codecortex/architecture/__init__.py +23 -0
  3. codecortex/architecture/drift.py +182 -0
  4. codecortex/architecture/inference.py +160 -0
  5. codecortex/backends/__init__.py +35 -0
  6. codecortex/backends/base.py +38 -0
  7. codecortex/backends/context.py +118 -0
  8. codecortex/backends/contracts.py +65 -0
  9. codecortex/backends/factory.py +60 -0
  10. codecortex/backends/graph.py +107 -0
  11. codecortex/backends/manager.py +303 -0
  12. codecortex/backends/mcp_client.py +196 -0
  13. codecortex/backends/pool.py +128 -0
  14. codecortex/backends/spec.py +83 -0
  15. codecortex/backends/symbols.py +189 -0
  16. codecortex/benchmark.py +211 -0
  17. codecortex/cli.py +409 -0
  18. codecortex/config.py +27 -0
  19. codecortex/context/__init__.py +13 -0
  20. codecortex/context/budget.py +62 -0
  21. codecortex/context/integrated.py +60 -0
  22. codecortex/context/pipeline.py +223 -0
  23. codecortex/core/__init__.py +1 -0
  24. codecortex/core/contracts.py +45 -0
  25. codecortex/core/errors.py +17 -0
  26. codecortex/core/models.py +69 -0
  27. codecortex/dashboard.py +259 -0
  28. codecortex/editing.py +41 -0
  29. codecortex/engines/__init__.py +5 -0
  30. codecortex/engines/builtin/__init__.py +5 -0
  31. codecortex/engines/builtin/factory.py +26 -0
  32. codecortex/engines/builtin/memory.py +35 -0
  33. codecortex/engines/builtin/repository.py +73 -0
  34. codecortex/engines/builtin/symbols.py +89 -0
  35. codecortex/engines/builtin/validation.py +54 -0
  36. codecortex/engines/registry.py +26 -0
  37. codecortex/entrypoint.py +266 -0
  38. codecortex/evaluation/__init__.py +55 -0
  39. codecortex/evaluation/external.py +265 -0
  40. codecortex/evaluation/production.py +670 -0
  41. codecortex/evaluation/regression.py +188 -0
  42. codecortex/gateway.py +38 -0
  43. codecortex/git_intelligence.py +252 -0
  44. codecortex/indexing/__init__.py +6 -0
  45. codecortex/indexing/graph.py +78 -0
  46. codecortex/indexing/impact.py +127 -0
  47. codecortex/indexing/incremental.py +163 -0
  48. codecortex/indexing/incremental_graph.py +189 -0
  49. codecortex/indexing/indexer.py +172 -0
  50. codecortex/indexing/relationships.py +179 -0
  51. codecortex/indexing/resolution.py +88 -0
  52. codecortex/integrations/__init__.py +5 -0
  53. codecortex/integrations/agents.py +235 -0
  54. codecortex/interfaces/__init__.py +1 -0
  55. codecortex/interfaces/mcp_bridge.py +66 -0
  56. codecortex/languages/__init__.py +5 -0
  57. codecortex/languages/native.py +166 -0
  58. codecortex/languages/registry.py +232 -0
  59. codecortex/mcp/__init__.py +5 -0
  60. codecortex/mcp/extended.py +114 -0
  61. codecortex/mcp/server.py +473 -0
  62. codecortex/memory/__init__.py +11 -0
  63. codecortex/memory/json_store.py +52 -0
  64. codecortex/memory/knowledge.py +193 -0
  65. codecortex/memory/team_store.py +193 -0
  66. codecortex/orchestrator.py +153 -0
  67. codecortex/pr_intelligence.py +214 -0
  68. codecortex/retrieval/__init__.py +16 -0
  69. codecortex/retrieval/hybrid.py +67 -0
  70. codecortex/retrieval/index.py +135 -0
  71. codecortex/retrieval/providers.py +67 -0
  72. codecortex/retrieval/repository.py +94 -0
  73. codecortex/router/__init__.py +5 -0
  74. codecortex/router/router.py +79 -0
  75. codecortex/runtime.py +69 -0
  76. codecortex/setup.py +100 -0
  77. codecortex/symbols/__init__.py +5 -0
  78. codecortex/symbols/providers.py +192 -0
  79. codecortex/telemetry/__init__.py +5 -0
  80. codecortex/telemetry/collector.py +43 -0
  81. codecortex/tracing/__init__.py +9 -0
  82. codecortex/tracing/task_trace.py +235 -0
  83. codecortex/workspace/__init__.py +9 -0
  84. codecortex/workspace/federation.py +173 -0
  85. codecortex_context_engine-0.1.0a1.dist-info/METADATA +381 -0
  86. codecortex_context_engine-0.1.0a1.dist-info/RECORD +90 -0
  87. codecortex_context_engine-0.1.0a1.dist-info/WHEEL +4 -0
  88. codecortex_context_engine-0.1.0a1.dist-info/entry_points.txt +3 -0
  89. codecortex_context_engine-0.1.0a1.dist-info/licenses/LICENSE +201 -0
  90. codecortex_context_engine-0.1.0a1.dist-info/licenses/NOTICE +2 -0
codecortex/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """CodeCortex package."""
2
+
3
+ __version__ = "0.1.0a1"
@@ -0,0 +1,23 @@
1
+ """Architecture inference and drift analysis."""
2
+
3
+ from codecortex.architecture.drift import (
4
+ ArchitectureDriftDetector,
5
+ ArchitectureDriftReport,
6
+ ArchitectureFingerprint,
7
+ DriftFinding,
8
+ )
9
+ from codecortex.architecture.inference import (
10
+ ArchitectureHypothesis,
11
+ ArchitectureInferenceEngine,
12
+ ArchitectureReport,
13
+ )
14
+
15
+ __all__ = [
16
+ "ArchitectureDriftDetector",
17
+ "ArchitectureDriftReport",
18
+ "ArchitectureFingerprint",
19
+ "ArchitectureHypothesis",
20
+ "ArchitectureInferenceEngine",
21
+ "ArchitectureReport",
22
+ "DriftFinding",
23
+ ]
@@ -0,0 +1,182 @@
1
+ """Architecture fingerprinting and drift detection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections import Counter
7
+ from dataclasses import asdict, dataclass
8
+ from pathlib import Path, PurePosixPath
9
+
10
+ from codecortex.architecture.inference import ArchitectureInferenceEngine
11
+ from codecortex.indexing.graph import ProjectGraph
12
+
13
+
14
+ @dataclass(frozen=True, slots=True)
15
+ class ArchitectureFingerprint:
16
+ version: int
17
+ pattern: str | None
18
+ confidence: float
19
+ dependency_counts: dict[str, int]
20
+ zone_file_counts: dict[str, int]
21
+ unresolved_ratio: float
22
+
23
+ def save(self, path: Path) -> None:
24
+ path.parent.mkdir(parents=True, exist_ok=True)
25
+ temp = path.with_suffix(path.suffix + ".tmp")
26
+ temp.write_text(json.dumps(asdict(self), indent=2, ensure_ascii=False), encoding="utf-8")
27
+ temp.replace(path)
28
+
29
+ @classmethod
30
+ def load(cls, path: Path) -> ArchitectureFingerprint | None:
31
+ try:
32
+ payload = json.loads(path.read_text(encoding="utf-8"))
33
+ return cls(
34
+ version=int(payload["version"]),
35
+ pattern=str(payload["pattern"]) if payload.get("pattern") else None,
36
+ confidence=float(payload["confidence"]),
37
+ dependency_counts={str(k): int(v) for k, v in payload["dependency_counts"].items()},
38
+ zone_file_counts={str(k): int(v) for k, v in payload["zone_file_counts"].items()},
39
+ unresolved_ratio=float(payload["unresolved_ratio"]),
40
+ )
41
+ except (OSError, json.JSONDecodeError, KeyError, TypeError, ValueError):
42
+ return None
43
+
44
+
45
+ @dataclass(frozen=True, slots=True)
46
+ class DriftFinding:
47
+ kind: str
48
+ severity: str
49
+ score: float
50
+ message: str
51
+ evidence: tuple[str, ...]
52
+
53
+
54
+ @dataclass(frozen=True, slots=True)
55
+ class ArchitectureDriftReport:
56
+ drifted: bool
57
+ score: float
58
+ findings: tuple[DriftFinding, ...]
59
+
60
+
61
+ class ArchitectureDriftDetector:
62
+ VERSION = 1
63
+
64
+ def fingerprint(self, graph: ProjectGraph) -> ArchitectureFingerprint:
65
+ report = ArchitectureInferenceEngine().analyze(graph)
66
+ node_map = {node.id: node for node in graph.nodes}
67
+ dependencies: Counter[str] = Counter()
68
+ zones: Counter[str] = Counter()
69
+ unresolved = 0
70
+ relevant_edges = 0
71
+ for node in graph.nodes:
72
+ if node.kind == "file" and node.path:
73
+ zones[self._zone(node.path)] += 1
74
+ for edge in graph.edges:
75
+ if edge.kind not in {"calls", "imports", "inherits", "implements"}:
76
+ continue
77
+ relevant_edges += 1
78
+ source = node_map.get(edge.source)
79
+ target = node_map.get(edge.target)
80
+ if target is None or target.kind in {"reference", "module"}:
81
+ unresolved += 1
82
+ source_zone = self._zone(source.path) if source and source.path else "external"
83
+ target_zone = self._zone(target.path) if target and target.path else "external"
84
+ dependencies[f"{source_zone}->{target_zone}:{edge.kind}"] += 1
85
+ primary = report.primary
86
+ return ArchitectureFingerprint(
87
+ version=self.VERSION,
88
+ pattern=primary.name if primary else None,
89
+ confidence=primary.confidence if primary else 0.0,
90
+ dependency_counts=dict(sorted(dependencies.items())),
91
+ zone_file_counts=dict(sorted(zones.items())),
92
+ unresolved_ratio=unresolved / max(1, relevant_edges),
93
+ )
94
+
95
+ def compare(
96
+ self,
97
+ baseline: ArchitectureFingerprint,
98
+ current: ArchitectureFingerprint,
99
+ dependency_growth_limit: float = 0.75,
100
+ ) -> ArchitectureDriftReport:
101
+ findings: list[DriftFinding] = []
102
+ if baseline.pattern and current.pattern and baseline.pattern != current.pattern:
103
+ findings.append(
104
+ DriftFinding(
105
+ "primary-pattern-change",
106
+ "high",
107
+ 0.90,
108
+ f"Primary architecture changed from {baseline.pattern} to {current.pattern}.",
109
+ (f"baseline={baseline.confidence:.2f}", f"current={current.confidence:.2f}"),
110
+ )
111
+ )
112
+ elif baseline.confidence - current.confidence >= 0.20:
113
+ findings.append(
114
+ DriftFinding(
115
+ "pattern-confidence-drop",
116
+ "medium",
117
+ 0.55,
118
+ "Architecture pattern confidence dropped materially.",
119
+ (f"{baseline.confidence:.2f}->{current.confidence:.2f}",),
120
+ )
121
+ )
122
+
123
+ for signature, count in current.dependency_counts.items():
124
+ before = baseline.dependency_counts.get(signature, 0)
125
+ if before == 0 and count > 0:
126
+ severity = "high" if any(kind in signature for kind in (":calls", ":inherits")) else "medium"
127
+ findings.append(
128
+ DriftFinding(
129
+ "new-dependency-direction",
130
+ severity,
131
+ 0.70 if severity == "high" else 0.45,
132
+ f"New dependency direction detected: {signature}.",
133
+ (f"count={count}",),
134
+ )
135
+ )
136
+ continue
137
+ growth = (count - before) / max(1, before)
138
+ if count - before >= 2 and growth > dependency_growth_limit:
139
+ findings.append(
140
+ DriftFinding(
141
+ "dependency-growth",
142
+ "medium",
143
+ min(0.70, 0.35 + growth * 0.20),
144
+ f"Dependency volume increased sharply: {signature}.",
145
+ (f"{before}->{count}", f"growth={growth:.0%}"),
146
+ )
147
+ )
148
+
149
+ unresolved_delta = current.unresolved_ratio - baseline.unresolved_ratio
150
+ if unresolved_delta >= 0.12:
151
+ findings.append(
152
+ DriftFinding(
153
+ "resolution-quality-drop",
154
+ "medium",
155
+ min(0.65, 0.40 + unresolved_delta),
156
+ "Unresolved dependency ratio increased.",
157
+ (
158
+ f"{baseline.unresolved_ratio:.2%}->{current.unresolved_ratio:.2%}",
159
+ ),
160
+ )
161
+ )
162
+
163
+ score = 1.0
164
+ for finding in findings:
165
+ score *= 1.0 - min(0.95, finding.score)
166
+ aggregate = round(1.0 - score, 4)
167
+ findings.sort(key=lambda item: (-item.score, item.kind))
168
+ return ArchitectureDriftReport(
169
+ drifted=bool(findings),
170
+ score=aggregate,
171
+ findings=tuple(findings),
172
+ )
173
+
174
+ @staticmethod
175
+ def _zone(path: str) -> str:
176
+ parts = PurePosixPath(path).parts
177
+ ignored = {"src", "lib", "app", "source"}
178
+ for part in parts[:-1]:
179
+ normalized = part.lower().replace("-", "_")
180
+ if normalized not in ignored:
181
+ return normalized
182
+ return "root"
@@ -0,0 +1,160 @@
1
+ """Evidence-based architecture pattern inference with calibrated confidence."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import PurePosixPath
7
+
8
+ from codecortex.indexing.graph import ProjectGraph
9
+
10
+
11
+ @dataclass(frozen=True, slots=True)
12
+ class ArchitectureHypothesis:
13
+ name: str
14
+ confidence: float
15
+ evidence: tuple[str, ...]
16
+ missing_signals: tuple[str, ...]
17
+
18
+
19
+ @dataclass(frozen=True, slots=True)
20
+ class ArchitectureReport:
21
+ primary: ArchitectureHypothesis | None
22
+ alternatives: tuple[ArchitectureHypothesis, ...]
23
+ analyzed_files: int
24
+ analyzed_symbols: int
25
+
26
+
27
+ @dataclass(frozen=True, slots=True)
28
+ class _Pattern:
29
+ name: str
30
+ groups: tuple[tuple[str, tuple[str, ...], float], ...]
31
+
32
+
33
+ class ArchitectureInferenceEngine:
34
+ """Infer architecture from path vocabulary, symbols, and graph relations.
35
+
36
+ Confidence is evidence-derived rather than a binary label. A pattern only reaches
37
+ high confidence when independent signal groups are present across the repository.
38
+ """
39
+
40
+ _PATTERNS = (
41
+ _Pattern(
42
+ "hexagonal",
43
+ (
44
+ ("domain", ("domain", "entities", "aggregate"), 0.24),
45
+ ("ports", ("ports", "port", "interfaces"), 0.24),
46
+ ("adapters", ("adapters", "adapter"), 0.22),
47
+ ("application", ("application", "usecases", "use_cases"), 0.18),
48
+ ("infrastructure", ("infrastructure", "infra"), 0.12),
49
+ ),
50
+ ),
51
+ _Pattern(
52
+ "clean-architecture",
53
+ (
54
+ ("domain", ("domain", "entities"), 0.25),
55
+ ("use-cases", ("usecases", "use_cases", "application"), 0.25),
56
+ ("interfaces", ("interfaces", "presenters", "controllers"), 0.20),
57
+ ("infrastructure", ("infrastructure", "frameworks", "drivers"), 0.20),
58
+ ("dependency-boundaries", ("repository", "gateway"), 0.10),
59
+ ),
60
+ ),
61
+ _Pattern(
62
+ "layered",
63
+ (
64
+ ("presentation", ("controllers", "controller", "api", "routes"), 0.22),
65
+ ("service", ("services", "service"), 0.24),
66
+ ("persistence", ("repositories", "repository", "dao"), 0.24),
67
+ ("model", ("models", "model", "entities"), 0.16),
68
+ ("configuration", ("config", "configuration"), 0.14),
69
+ ),
70
+ ),
71
+ _Pattern(
72
+ "mvc",
73
+ (
74
+ ("models", ("models", "model"), 0.34),
75
+ ("views", ("views", "templates", "view"), 0.33),
76
+ ("controllers", ("controllers", "controller"), 0.33),
77
+ ),
78
+ ),
79
+ _Pattern(
80
+ "service-repository",
81
+ (
82
+ ("services", ("services", "service"), 0.38),
83
+ ("repositories", ("repositories", "repository"), 0.38),
84
+ ("models", ("models", "entities", "model"), 0.14),
85
+ ("contracts", ("protocol", "interface", "contracts"), 0.10),
86
+ ),
87
+ ),
88
+ _Pattern(
89
+ "monorepo",
90
+ (
91
+ ("apps", ("apps", "applications"), 0.30),
92
+ ("packages", ("packages", "libs", "libraries"), 0.30),
93
+ ("workspace", ("workspace", "workspaces"), 0.15),
94
+ ("shared", ("shared", "common"), 0.15),
95
+ ("tooling", ("tools", "tooling"), 0.10),
96
+ ),
97
+ ),
98
+ _Pattern(
99
+ "plugin-oriented",
100
+ (
101
+ ("plugins", ("plugins", "plugin", "extensions"), 0.42),
102
+ ("adapters", ("adapters", "providers"), 0.22),
103
+ ("registry", ("registry", "registries"), 0.18),
104
+ ("hooks", ("hooks", "hook"), 0.18),
105
+ ),
106
+ ),
107
+ )
108
+
109
+ def analyze(self, graph: ProjectGraph, threshold: float = 0.20) -> ArchitectureReport:
110
+ corpus = self._corpus(graph)
111
+ hypotheses = [self._score(pattern, corpus) for pattern in self._PATTERNS]
112
+ hypotheses = [item for item in hypotheses if item.confidence >= threshold]
113
+ hypotheses.sort(key=lambda item: (-item.confidence, item.name))
114
+ return ArchitectureReport(
115
+ primary=hypotheses[0] if hypotheses else None,
116
+ alternatives=tuple(hypotheses[1:4]),
117
+ analyzed_files=sum(1 for node in graph.nodes if node.kind == "file"),
118
+ analyzed_symbols=sum(
119
+ 1 for node in graph.nodes if node.kind not in {"file", "module", "reference"}
120
+ ),
121
+ )
122
+
123
+ @staticmethod
124
+ def _corpus(graph: ProjectGraph) -> set[str]:
125
+ terms: set[str] = set()
126
+ for node in graph.nodes:
127
+ if node.path:
128
+ path = PurePosixPath(node.path)
129
+ for part in path.parts:
130
+ stem = PurePosixPath(part).stem.lower().replace("-", "_")
131
+ terms.add(stem)
132
+ terms.update(piece for piece in stem.split("_") if piece)
133
+ name = node.name.lower().replace("-", "_")
134
+ terms.add(name)
135
+ terms.update(piece for piece in name.split("_") if piece)
136
+ return terms
137
+
138
+ @staticmethod
139
+ def _score(pattern: _Pattern, corpus: set[str]) -> ArchitectureHypothesis:
140
+ confidence = 0.0
141
+ evidence: list[str] = []
142
+ missing: list[str] = []
143
+ matched_groups = 0
144
+ for label, aliases, weight in pattern.groups:
145
+ matches = sorted(alias for alias in aliases if alias in corpus)
146
+ if matches:
147
+ confidence += weight
148
+ matched_groups += 1
149
+ evidence.append(f"{label}: {', '.join(matches[:3])}")
150
+ else:
151
+ missing.append(label)
152
+ # Independent groups matter more than repeated vocabulary from one layer.
153
+ diversity = matched_groups / max(1, len(pattern.groups))
154
+ confidence *= 0.70 + 0.30 * diversity
155
+ return ArchitectureHypothesis(
156
+ name=pattern.name,
157
+ confidence=round(min(1.0, confidence), 4),
158
+ evidence=tuple(evidence),
159
+ missing_signals=tuple(missing),
160
+ )
@@ -0,0 +1,35 @@
1
+ """Stable optional backend boundary."""
2
+
3
+ from codecortex.backends.context import ContextBackendAdapter
4
+ from codecortex.backends.contracts import (
5
+ BackendCompatibilityError,
6
+ BackendStatus,
7
+ ContextIntelligence,
8
+ GraphIntelligence,
9
+ ManagedBackend,
10
+ SymbolIntelligence,
11
+ )
12
+ from codecortex.backends.graph import GraphBackendAdapter
13
+ from codecortex.backends.manager import BackendManager, BackendProcessError, ProcessResult
14
+ from codecortex.backends.mcp_client import MCPError, MCPStdioClient
15
+ from codecortex.backends.spec import BACKENDS, BackendSpec
16
+ from codecortex.backends.symbols import SymbolBackendAdapter
17
+
18
+ __all__ = [
19
+ "BACKENDS",
20
+ "BackendCompatibilityError",
21
+ "BackendManager",
22
+ "BackendProcessError",
23
+ "BackendSpec",
24
+ "BackendStatus",
25
+ "ContextBackendAdapter",
26
+ "ContextIntelligence",
27
+ "GraphBackendAdapter",
28
+ "GraphIntelligence",
29
+ "MCPError",
30
+ "MCPStdioClient",
31
+ "ManagedBackend",
32
+ "ProcessResult",
33
+ "SymbolBackendAdapter",
34
+ "SymbolIntelligence",
35
+ ]
@@ -0,0 +1,38 @@
1
+ """Common adapter behavior and compatibility reporting."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from codecortex.backends.contracts import BackendCompatibilityError, BackendStatus
6
+ from codecortex.backends.manager import BackendManager
7
+ from codecortex.backends.spec import BackendSpec
8
+
9
+
10
+ class ManagedAdapterMixin:
11
+ contract_version = 1
12
+ manager: BackendManager
13
+ spec: BackendSpec
14
+
15
+ def status(self) -> BackendStatus:
16
+ installed = self.manager.is_installed(self.spec)
17
+ healthy = self.manager.probe(self.spec, provision=False) if installed else False
18
+ return BackendStatus(
19
+ key=self.spec.key,
20
+ installed=installed,
21
+ healthy=healthy,
22
+ revision=self.spec.revision,
23
+ contract_version=self.contract_version,
24
+ capabilities=self.spec.capabilities,
25
+ )
26
+
27
+ @staticmethod
28
+ def require_tools(catalog: list[dict[str, object]], required: set[str]) -> None:
29
+ available = {
30
+ str(item.get("name"))
31
+ for item in catalog
32
+ if isinstance(item.get("name"), str)
33
+ }
34
+ missing = sorted(required - available)
35
+ if missing:
36
+ raise BackendCompatibilityError(
37
+ "backend tool contract mismatch; missing: " + ", ".join(missing)
38
+ )
@@ -0,0 +1,118 @@
1
+ """Configurable context-processing backend adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ from collections.abc import Mapping, Sequence
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from codecortex.backends.base import ManagedAdapterMixin
12
+ from codecortex.backends.manager import BackendManager
13
+ from codecortex.backends.mcp_client import MCPStdioClient
14
+ from codecortex.backends.pool import BackendSessionPool
15
+ from codecortex.backends.spec import BACKENDS
16
+ from codecortex.core.contracts import Engine
17
+ from codecortex.core.models import AgentRequest, Capability, ContextChunk, EngineResult
18
+
19
+
20
+ class ContextBackendAdapter(ManagedAdapterMixin, Engine):
21
+ capability = Capability.CONTEXT
22
+ required_tools = {"context_compress", "context_retrieve", "context_stats"}
23
+
24
+ def __init__(self, project_root: Path, manager: BackendManager | None = None) -> None:
25
+ self.project_root = project_root.resolve()
26
+ self.manager = manager or BackendManager()
27
+ self.spec = BACKENDS["context"]
28
+ self.pool = BackendSessionPool(self.manager)
29
+
30
+ async def health(self) -> bool:
31
+ return await asyncio.to_thread(self.manager.probe, self.spec, False)
32
+
33
+ def server_args(self) -> tuple[str, ...]:
34
+ return ("mcp", "serve", "--transport", "stdio")
35
+
36
+ def _env(self) -> dict[str, str]:
37
+ workspace = self.project_root / ".codecortex" / "context-backend"
38
+ workspace.mkdir(parents=True, exist_ok=True)
39
+ return {"CODECORTEX_CONTEXT_WORKSPACE_DIR": str(workspace)}
40
+
41
+ def _client(self) -> MCPStdioClient:
42
+ return MCPStdioClient(
43
+ self.manager,
44
+ self.spec,
45
+ self.server_args(),
46
+ cwd=self.project_root,
47
+ env=self._env(),
48
+ )
49
+
50
+ def tools(self) -> list[dict[str, Any]]:
51
+ tools = self.pool.tools(
52
+ self.spec,
53
+ self.server_args(),
54
+ cwd=self.project_root,
55
+ env=self._env(),
56
+ )
57
+ self.require_tools(tools, self.required_tools)
58
+ return tools
59
+
60
+ def compress(self, content: str) -> dict[str, Any]:
61
+ return self.call("context_compress", {"content": content})
62
+
63
+ def compress_batch(self, contents: Sequence[str]) -> list[dict[str, Any]]:
64
+ return [self.compress(content) for content in contents]
65
+
66
+ def retrieve(self, hash_key: str) -> dict[str, Any]:
67
+ return self.call("context_retrieve", {"hash": hash_key})
68
+
69
+ def stats(self) -> dict[str, Any]:
70
+ return self.call("context_stats", {})
71
+
72
+ def call(self, tool: str, arguments: Mapping[str, Any]) -> dict[str, Any]:
73
+ return self.pool.call_tool(
74
+ self.spec,
75
+ self.server_args(),
76
+ tool,
77
+ arguments,
78
+ cwd=self.project_root,
79
+ env=self._env(),
80
+ )
81
+
82
+ async def execute(self, request: AgentRequest) -> EngineResult:
83
+ return await asyncio.to_thread(self._execute_sync, request)
84
+
85
+ def _execute_sync(self, request: AgentRequest) -> EngineResult:
86
+ tool = request.metadata.get("context_tool")
87
+ arguments = request.metadata.get("context_arguments")
88
+ payload = (
89
+ self.call(tool, dict(arguments) if isinstance(arguments, Mapping) else {})
90
+ if isinstance(tool, str)
91
+ else self.compress(request.query)
92
+ )
93
+ tool = tool if isinstance(tool, str) else "context_compress"
94
+ content = MCPStdioClient.content_text(payload) or json.dumps(payload, ensure_ascii=False)
95
+ metadata: dict[str, Any] = {
96
+ "backend": self.spec.key,
97
+ "revision": self.spec.revision,
98
+ "tool": tool,
99
+ }
100
+ structured = payload.get("structuredContent")
101
+ if isinstance(structured, dict):
102
+ metadata["compression"] = structured
103
+ return EngineResult(
104
+ capability=self.capability,
105
+ content=content,
106
+ chunks=[
107
+ ContextChunk(
108
+ source=f"context:{tool}",
109
+ content=content,
110
+ tokens=max(1, len(content) // 4),
111
+ relevance=1.0,
112
+ metadata=metadata,
113
+ )
114
+ ]
115
+ if content
116
+ else [],
117
+ metadata=metadata,
118
+ )
@@ -0,0 +1,65 @@
1
+ """Stable CodeCortex-side contracts for optional mature backends.
2
+
3
+ These interfaces are the compatibility boundary. CodeCortex code outside this
4
+ package must not import backend implementation modules or internal APIs.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Mapping
10
+ from dataclasses import dataclass
11
+ from typing import Any, Protocol, runtime_checkable
12
+
13
+ from codecortex.backends.spec import BackendSpec
14
+
15
+
16
+ @dataclass(frozen=True, slots=True)
17
+ class BackendStatus:
18
+ key: str
19
+ installed: bool
20
+ healthy: bool
21
+ revision: str
22
+ contract_version: int
23
+ capabilities: tuple[str, ...]
24
+ detail: str = ""
25
+
26
+
27
+ class BackendCompatibilityError(RuntimeError):
28
+ pass
29
+
30
+
31
+ @runtime_checkable
32
+ class ManagedBackend(Protocol):
33
+ spec: BackendSpec
34
+ contract_version: int
35
+
36
+ async def health(self) -> bool: ...
37
+
38
+ def status(self) -> BackendStatus: ...
39
+
40
+
41
+ @runtime_checkable
42
+ class GraphIntelligence(ManagedBackend, Protocol):
43
+ def build(self) -> dict[str, Any]: ...
44
+
45
+ def query(self, query: str) -> str: ...
46
+
47
+ def explain(self, node: str) -> str: ...
48
+
49
+ def path(self, source: str, target: str) -> str: ...
50
+
51
+
52
+ @runtime_checkable
53
+ class SymbolIntelligence(ManagedBackend, Protocol):
54
+ def tools(self) -> list[dict[str, Any]]: ...
55
+
56
+ def call(self, tool: str, arguments: Mapping[str, Any]) -> dict[str, Any]: ...
57
+
58
+
59
+ @runtime_checkable
60
+ class ContextIntelligence(ManagedBackend, Protocol):
61
+ def compress(self, content: str) -> dict[str, Any]: ...
62
+
63
+ def retrieve(self, hash_key: str) -> dict[str, Any]: ...
64
+
65
+ def stats(self) -> dict[str, Any]: ...
@@ -0,0 +1,60 @@
1
+ """Assembly of CodeCortex-owned intelligence with optional configured adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from dataclasses import dataclass
7
+
8
+ from codecortex.backends.context import ContextBackendAdapter
9
+ from codecortex.backends.graph import GraphBackendAdapter
10
+ from codecortex.backends.manager import BackendManager
11
+ from codecortex.backends.spec import BACKENDS
12
+ from codecortex.backends.symbols import SymbolBackendAdapter
13
+ from codecortex.config import CortexConfig
14
+ from codecortex.context import IntegratedContextProcessor
15
+ from codecortex.core.contracts import ContextProcessor, MemoryStore
16
+ from codecortex.engines import EngineRegistry
17
+ from codecortex.engines.builtin import build_default_registry
18
+
19
+
20
+ @dataclass(slots=True)
21
+ class BackendStack:
22
+ registry: EngineRegistry
23
+ context_processor: ContextProcessor
24
+ manager: BackendManager
25
+ active: tuple[str, ...]
26
+
27
+
28
+ def build_backend_stack(config: CortexConfig, memory_store: MemoryStore) -> BackendStack:
29
+ registry = build_default_registry(config, memory_store=memory_store)
30
+ manager = BackendManager()
31
+ mode = os.getenv("CODECORTEX_BACKENDS", "builtin").strip().lower()
32
+ if mode == "mature":
33
+ mode = "external"
34
+ if mode not in {"auto", "builtin", "external"}:
35
+ mode = "builtin"
36
+
37
+ active: list[str] = []
38
+ context_backend: ContextBackendAdapter | None = None
39
+ if mode != "builtin":
40
+ graph = GraphBackendAdapter(config.project_root, manager)
41
+ symbols = SymbolBackendAdapter(config.project_root, manager)
42
+ context = ContextBackendAdapter(config.project_root, manager)
43
+ for key, adapter in (("graph", graph), ("symbols", symbols)):
44
+ spec = BACKENDS[key]
45
+ if spec.configured and (mode == "external" or manager.is_installed(spec)):
46
+ registry.register(adapter)
47
+ active.append(key)
48
+ context_spec = BACKENDS["context"]
49
+ if context_spec.configured and (
50
+ mode == "external" or manager.is_installed(context_spec)
51
+ ):
52
+ context_backend = context
53
+ active.append("context")
54
+
55
+ return BackendStack(
56
+ registry=registry,
57
+ context_processor=IntegratedContextProcessor(context_backend),
58
+ manager=manager,
59
+ active=tuple(active),
60
+ )