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.
- codecortex/__init__.py +3 -0
- codecortex/architecture/__init__.py +23 -0
- codecortex/architecture/drift.py +182 -0
- codecortex/architecture/inference.py +160 -0
- codecortex/backends/__init__.py +35 -0
- codecortex/backends/base.py +38 -0
- codecortex/backends/context.py +118 -0
- codecortex/backends/contracts.py +65 -0
- codecortex/backends/factory.py +60 -0
- codecortex/backends/graph.py +107 -0
- codecortex/backends/manager.py +303 -0
- codecortex/backends/mcp_client.py +196 -0
- codecortex/backends/pool.py +128 -0
- codecortex/backends/spec.py +83 -0
- codecortex/backends/symbols.py +189 -0
- codecortex/benchmark.py +211 -0
- codecortex/cli.py +409 -0
- codecortex/config.py +27 -0
- codecortex/context/__init__.py +13 -0
- codecortex/context/budget.py +62 -0
- codecortex/context/integrated.py +60 -0
- codecortex/context/pipeline.py +223 -0
- codecortex/core/__init__.py +1 -0
- codecortex/core/contracts.py +45 -0
- codecortex/core/errors.py +17 -0
- codecortex/core/models.py +69 -0
- codecortex/dashboard.py +259 -0
- codecortex/editing.py +41 -0
- codecortex/engines/__init__.py +5 -0
- codecortex/engines/builtin/__init__.py +5 -0
- codecortex/engines/builtin/factory.py +26 -0
- codecortex/engines/builtin/memory.py +35 -0
- codecortex/engines/builtin/repository.py +73 -0
- codecortex/engines/builtin/symbols.py +89 -0
- codecortex/engines/builtin/validation.py +54 -0
- codecortex/engines/registry.py +26 -0
- codecortex/entrypoint.py +266 -0
- codecortex/evaluation/__init__.py +55 -0
- codecortex/evaluation/external.py +265 -0
- codecortex/evaluation/production.py +670 -0
- codecortex/evaluation/regression.py +188 -0
- codecortex/gateway.py +38 -0
- codecortex/git_intelligence.py +252 -0
- codecortex/indexing/__init__.py +6 -0
- codecortex/indexing/graph.py +78 -0
- codecortex/indexing/impact.py +127 -0
- codecortex/indexing/incremental.py +163 -0
- codecortex/indexing/incremental_graph.py +189 -0
- codecortex/indexing/indexer.py +172 -0
- codecortex/indexing/relationships.py +179 -0
- codecortex/indexing/resolution.py +88 -0
- codecortex/integrations/__init__.py +5 -0
- codecortex/integrations/agents.py +235 -0
- codecortex/interfaces/__init__.py +1 -0
- codecortex/interfaces/mcp_bridge.py +66 -0
- codecortex/languages/__init__.py +5 -0
- codecortex/languages/native.py +166 -0
- codecortex/languages/registry.py +232 -0
- codecortex/mcp/__init__.py +5 -0
- codecortex/mcp/extended.py +114 -0
- codecortex/mcp/server.py +473 -0
- codecortex/memory/__init__.py +11 -0
- codecortex/memory/json_store.py +52 -0
- codecortex/memory/knowledge.py +193 -0
- codecortex/memory/team_store.py +193 -0
- codecortex/orchestrator.py +153 -0
- codecortex/pr_intelligence.py +214 -0
- codecortex/retrieval/__init__.py +16 -0
- codecortex/retrieval/hybrid.py +67 -0
- codecortex/retrieval/index.py +135 -0
- codecortex/retrieval/providers.py +67 -0
- codecortex/retrieval/repository.py +94 -0
- codecortex/router/__init__.py +5 -0
- codecortex/router/router.py +79 -0
- codecortex/runtime.py +69 -0
- codecortex/setup.py +100 -0
- codecortex/symbols/__init__.py +5 -0
- codecortex/symbols/providers.py +192 -0
- codecortex/telemetry/__init__.py +5 -0
- codecortex/telemetry/collector.py +43 -0
- codecortex/tracing/__init__.py +9 -0
- codecortex/tracing/task_trace.py +235 -0
- codecortex/workspace/__init__.py +9 -0
- codecortex/workspace/federation.py +173 -0
- codecortex_context_engine-0.1.0a1.dist-info/METADATA +381 -0
- codecortex_context_engine-0.1.0a1.dist-info/RECORD +90 -0
- codecortex_context_engine-0.1.0a1.dist-info/WHEEL +4 -0
- codecortex_context_engine-0.1.0a1.dist-info/entry_points.txt +3 -0
- codecortex_context_engine-0.1.0a1.dist-info/licenses/LICENSE +201 -0
- codecortex_context_engine-0.1.0a1.dist-info/licenses/NOTICE +2 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Cross-file symbol resolution with explicit confidence and ambiguity."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import PurePosixPath
|
|
7
|
+
|
|
8
|
+
from codecortex.indexing.graph import GraphNode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True, slots=True)
|
|
12
|
+
class ResolutionCandidate:
|
|
13
|
+
node_id: str
|
|
14
|
+
score: float
|
|
15
|
+
reasons: tuple[str, ...]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True, slots=True)
|
|
19
|
+
class ResolutionResult:
|
|
20
|
+
target_id: str | None
|
|
21
|
+
confidence: float
|
|
22
|
+
ambiguity: float
|
|
23
|
+
candidates: tuple[ResolutionCandidate, ...]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CrossFileResolver:
|
|
27
|
+
"""Rank same-name symbols while preserving uncertainty for downstream agents."""
|
|
28
|
+
|
|
29
|
+
def resolve(
|
|
30
|
+
self,
|
|
31
|
+
name: str,
|
|
32
|
+
source_path: str,
|
|
33
|
+
candidates: list[GraphNode],
|
|
34
|
+
relation_kind: str,
|
|
35
|
+
) -> ResolutionResult:
|
|
36
|
+
ranked: list[ResolutionCandidate] = []
|
|
37
|
+
source = PurePosixPath(source_path)
|
|
38
|
+
for node in candidates:
|
|
39
|
+
if node.path is None:
|
|
40
|
+
continue
|
|
41
|
+
target = PurePosixPath(node.path)
|
|
42
|
+
score = 0.35
|
|
43
|
+
reasons = ["exact_symbol_name"]
|
|
44
|
+
if target == source:
|
|
45
|
+
score += 0.35
|
|
46
|
+
reasons.append("same_file")
|
|
47
|
+
elif target.parent == source.parent:
|
|
48
|
+
score += 0.20
|
|
49
|
+
reasons.append("same_directory")
|
|
50
|
+
else:
|
|
51
|
+
shared = len(set(source.parts[:-1]) & set(target.parts[:-1]))
|
|
52
|
+
if shared:
|
|
53
|
+
score += min(0.12, shared * 0.03)
|
|
54
|
+
reasons.append("shared_package_path")
|
|
55
|
+
if relation_kind == "calls" and node.kind in {
|
|
56
|
+
"function",
|
|
57
|
+
"async_function",
|
|
58
|
+
"method",
|
|
59
|
+
}:
|
|
60
|
+
score += 0.08
|
|
61
|
+
reasons.append("callable_kind")
|
|
62
|
+
if relation_kind in {"inherits", "implements"} and node.kind in {
|
|
63
|
+
"class",
|
|
64
|
+
"interface",
|
|
65
|
+
}:
|
|
66
|
+
score += 0.08
|
|
67
|
+
reasons.append("type_kind")
|
|
68
|
+
ranked.append(
|
|
69
|
+
ResolutionCandidate(
|
|
70
|
+
node_id=node.id,
|
|
71
|
+
score=min(1.0, score),
|
|
72
|
+
reasons=tuple(reasons),
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
ranked.sort(key=lambda item: (-item.score, item.node_id))
|
|
76
|
+
if not ranked:
|
|
77
|
+
return ResolutionResult(None, 0.0, 1.0, ())
|
|
78
|
+
best = ranked[0]
|
|
79
|
+
second = ranked[1].score if len(ranked) > 1 else 0.0
|
|
80
|
+
margin = max(0.0, best.score - second)
|
|
81
|
+
ambiguity = 0.0 if len(ranked) == 1 else max(0.0, min(1.0, 1.0 - margin))
|
|
82
|
+
confidence = max(0.0, min(1.0, best.score * (1.0 - 0.45 * ambiguity)))
|
|
83
|
+
return ResolutionResult(
|
|
84
|
+
target_id=best.node_id,
|
|
85
|
+
confidence=confidence,
|
|
86
|
+
ambiguity=ambiguity,
|
|
87
|
+
candidates=tuple(ranked[:5]),
|
|
88
|
+
)
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Merge-safe project configuration for MCP-capable coding agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import shutil
|
|
9
|
+
import tempfile
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from datetime import UTC, datetime
|
|
12
|
+
from enum import StrEnum
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgentTarget(StrEnum):
|
|
18
|
+
CLAUDE = "claude"
|
|
19
|
+
CODEX = "codex"
|
|
20
|
+
CURSOR = "cursor"
|
|
21
|
+
GEMINI = "gemini"
|
|
22
|
+
OPENCODE = "opencode"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class AgentMutation:
|
|
27
|
+
target: AgentTarget
|
|
28
|
+
path: Path
|
|
29
|
+
detected: bool
|
|
30
|
+
changed: bool
|
|
31
|
+
backup: Path | None = None
|
|
32
|
+
detail: str = ""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AgentConfigurationError(RuntimeError):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AgentConfigurator:
|
|
40
|
+
"""Configure project-local integrations without clobbering user-owned settings."""
|
|
41
|
+
|
|
42
|
+
COMMANDS = {
|
|
43
|
+
AgentTarget.CLAUDE: ("claude",),
|
|
44
|
+
AgentTarget.CODEX: ("codex",),
|
|
45
|
+
AgentTarget.GEMINI: ("gemini",),
|
|
46
|
+
AgentTarget.OPENCODE: ("opencode",),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def __init__(self, root: Path, *, executable: str = "cortex") -> None:
|
|
50
|
+
self.root = root.expanduser().resolve()
|
|
51
|
+
self.executable = executable
|
|
52
|
+
|
|
53
|
+
def detect(self) -> tuple[AgentTarget, ...]:
|
|
54
|
+
found: set[AgentTarget] = set()
|
|
55
|
+
for target, commands in self.COMMANDS.items():
|
|
56
|
+
if any(shutil.which(command) for command in commands):
|
|
57
|
+
found.add(target)
|
|
58
|
+
if (self.root / ".cursor").exists() or shutil.which("cursor"):
|
|
59
|
+
found.add(AgentTarget.CURSOR)
|
|
60
|
+
if (self.root / ".mcp.json").exists():
|
|
61
|
+
found.add(AgentTarget.CLAUDE)
|
|
62
|
+
if (self.root / ".gemini").exists():
|
|
63
|
+
found.add(AgentTarget.GEMINI)
|
|
64
|
+
if (self.root / ".codex").exists():
|
|
65
|
+
found.add(AgentTarget.CODEX)
|
|
66
|
+
if (self.root / "opencode.json").exists():
|
|
67
|
+
found.add(AgentTarget.OPENCODE)
|
|
68
|
+
return tuple(sorted(found, key=str))
|
|
69
|
+
|
|
70
|
+
def configure(
|
|
71
|
+
self,
|
|
72
|
+
targets: tuple[AgentTarget, ...] | None = None,
|
|
73
|
+
*,
|
|
74
|
+
dry_run: bool = False,
|
|
75
|
+
) -> tuple[AgentMutation, ...]:
|
|
76
|
+
selected = targets or self.detect()
|
|
77
|
+
return tuple(self.configure_one(target, dry_run=dry_run) for target in selected)
|
|
78
|
+
|
|
79
|
+
def configure_one(self, target: AgentTarget, *, dry_run: bool = False) -> AgentMutation:
|
|
80
|
+
detected = target in self.detect()
|
|
81
|
+
if target == AgentTarget.CLAUDE:
|
|
82
|
+
return self._configure_json(
|
|
83
|
+
target,
|
|
84
|
+
self.root / ".mcp.json",
|
|
85
|
+
("mcpServers",),
|
|
86
|
+
self._stdio_json(),
|
|
87
|
+
detected,
|
|
88
|
+
dry_run,
|
|
89
|
+
)
|
|
90
|
+
if target == AgentTarget.CURSOR:
|
|
91
|
+
return self._configure_json(
|
|
92
|
+
target,
|
|
93
|
+
self.root / ".cursor" / "mcp.json",
|
|
94
|
+
("mcpServers",),
|
|
95
|
+
self._stdio_json(),
|
|
96
|
+
detected,
|
|
97
|
+
dry_run,
|
|
98
|
+
)
|
|
99
|
+
if target == AgentTarget.GEMINI:
|
|
100
|
+
return self._configure_json(
|
|
101
|
+
target,
|
|
102
|
+
self.root / ".gemini" / "settings.json",
|
|
103
|
+
("mcpServers",),
|
|
104
|
+
self._stdio_json(),
|
|
105
|
+
detected,
|
|
106
|
+
dry_run,
|
|
107
|
+
)
|
|
108
|
+
if target == AgentTarget.OPENCODE:
|
|
109
|
+
return self._configure_json(
|
|
110
|
+
target,
|
|
111
|
+
self.root / "opencode.json",
|
|
112
|
+
("mcp", "servers"),
|
|
113
|
+
{
|
|
114
|
+
"type": "local",
|
|
115
|
+
"command": [self.executable, "mcp", "--path", str(self.root)],
|
|
116
|
+
},
|
|
117
|
+
detected,
|
|
118
|
+
dry_run,
|
|
119
|
+
defaults={"$schema": "https://opencode.ai/config.json"},
|
|
120
|
+
)
|
|
121
|
+
if target == AgentTarget.CODEX:
|
|
122
|
+
return self._configure_codex(detected=detected, dry_run=dry_run)
|
|
123
|
+
raise ValueError(target)
|
|
124
|
+
|
|
125
|
+
def _stdio_json(self) -> dict[str, Any]:
|
|
126
|
+
return {
|
|
127
|
+
"command": self.executable,
|
|
128
|
+
"args": ["mcp", "--path", str(self.root)],
|
|
129
|
+
"env": {"CODECORTEX_BACKENDS": "auto"},
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
def _configure_json(
|
|
133
|
+
self,
|
|
134
|
+
target: AgentTarget,
|
|
135
|
+
path: Path,
|
|
136
|
+
container_keys: tuple[str, ...],
|
|
137
|
+
server: dict[str, Any],
|
|
138
|
+
detected: bool,
|
|
139
|
+
dry_run: bool,
|
|
140
|
+
*,
|
|
141
|
+
defaults: dict[str, Any] | None = None,
|
|
142
|
+
) -> AgentMutation:
|
|
143
|
+
payload: dict[str, Any] = dict(defaults or {})
|
|
144
|
+
if path.exists():
|
|
145
|
+
try:
|
|
146
|
+
loaded = json.loads(path.read_text(encoding="utf-8"))
|
|
147
|
+
except json.JSONDecodeError as exc:
|
|
148
|
+
raise AgentConfigurationError(f"refusing to modify invalid JSON: {path}: {exc}") from exc
|
|
149
|
+
if not isinstance(loaded, dict):
|
|
150
|
+
raise AgentConfigurationError(f"expected a JSON object: {path}")
|
|
151
|
+
payload = loaded
|
|
152
|
+
for key, value in (defaults or {}).items():
|
|
153
|
+
payload.setdefault(key, value)
|
|
154
|
+
container: dict[str, Any] = payload
|
|
155
|
+
for key in container_keys:
|
|
156
|
+
child = container.get(key)
|
|
157
|
+
if child is None:
|
|
158
|
+
child = {}
|
|
159
|
+
container[key] = child
|
|
160
|
+
if not isinstance(child, dict):
|
|
161
|
+
raise AgentConfigurationError(f"cannot merge CodeCortex into non-object {'.'.join(container_keys)} in {path}")
|
|
162
|
+
container = child
|
|
163
|
+
previous = container.get("codecortex")
|
|
164
|
+
container["codecortex"] = server
|
|
165
|
+
changed = previous != server or not path.exists()
|
|
166
|
+
if not changed or dry_run:
|
|
167
|
+
return AgentMutation(target, path, detected, changed, detail="dry-run" if dry_run and changed else "already configured")
|
|
168
|
+
backup = self._backup(path)
|
|
169
|
+
self._atomic_write(path, json.dumps(payload, ensure_ascii=False, indent=2) + "\n")
|
|
170
|
+
return AgentMutation(target, path, detected, True, backup=backup, detail="configured")
|
|
171
|
+
|
|
172
|
+
def _configure_codex(self, *, detected: bool, dry_run: bool) -> AgentMutation:
|
|
173
|
+
path = self.root / ".codex" / "config.toml"
|
|
174
|
+
begin = "# >>> codecortex managed mcp >>>"
|
|
175
|
+
end = "# <<< codecortex managed mcp <<<"
|
|
176
|
+
args = ["mcp", "--path", str(self.root)]
|
|
177
|
+
block = "\n".join(
|
|
178
|
+
[
|
|
179
|
+
begin,
|
|
180
|
+
"[mcp_servers.codecortex]",
|
|
181
|
+
f"command = {json.dumps(self.executable)}",
|
|
182
|
+
f"args = {json.dumps(args)}",
|
|
183
|
+
"env = { CODECORTEX_BACKENDS = \"auto\" }",
|
|
184
|
+
"enabled = true",
|
|
185
|
+
"startup_timeout_sec = 30",
|
|
186
|
+
"tool_timeout_sec = 120",
|
|
187
|
+
end,
|
|
188
|
+
]
|
|
189
|
+
)
|
|
190
|
+
existing = path.read_text(encoding="utf-8") if path.exists() else ""
|
|
191
|
+
managed = re.compile(re.escape(begin) + r".*?" + re.escape(end), re.DOTALL)
|
|
192
|
+
if managed.search(existing):
|
|
193
|
+
# A callable replacement keeps Windows backslashes literal instead of
|
|
194
|
+
# letting re.sub interpret them as replacement-string escapes.
|
|
195
|
+
updated = managed.sub(lambda _match: block, existing)
|
|
196
|
+
else:
|
|
197
|
+
if re.search(r"(?m)^\s*\[mcp_servers\.codecortex\]\s*$", existing):
|
|
198
|
+
raise AgentConfigurationError(
|
|
199
|
+
f"refusing to overwrite an unmanaged [mcp_servers.codecortex] table in {path}"
|
|
200
|
+
)
|
|
201
|
+
separator = "\n\n" if existing.strip() else ""
|
|
202
|
+
updated = existing.rstrip() + separator + block + "\n"
|
|
203
|
+
changed = updated != existing
|
|
204
|
+
if not changed or dry_run:
|
|
205
|
+
return AgentMutation(AgentTarget.CODEX, path, detected, changed, detail="dry-run" if dry_run and changed else "already configured")
|
|
206
|
+
backup = self._backup(path)
|
|
207
|
+
self._atomic_write(path, updated)
|
|
208
|
+
return AgentMutation(AgentTarget.CODEX, path, detected, True, backup=backup, detail="configured")
|
|
209
|
+
|
|
210
|
+
@staticmethod
|
|
211
|
+
def _backup(path: Path) -> Path | None:
|
|
212
|
+
if not path.exists():
|
|
213
|
+
return None
|
|
214
|
+
stamp = datetime.now(UTC).strftime("%Y%m%d-%H%M%S-%f")
|
|
215
|
+
backup = path.with_name(f"{path.name}.codecortex-{stamp}.bak")
|
|
216
|
+
backup.parent.mkdir(parents=True, exist_ok=True)
|
|
217
|
+
shutil.copy2(path, backup)
|
|
218
|
+
return backup
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def _atomic_write(path: Path, content: str) -> None:
|
|
222
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
223
|
+
fd, temp_name = tempfile.mkstemp(prefix=f".{path.name}.", suffix=".tmp", dir=path.parent)
|
|
224
|
+
try:
|
|
225
|
+
with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as handle:
|
|
226
|
+
handle.write(content)
|
|
227
|
+
handle.flush()
|
|
228
|
+
os.fsync(handle.fileno())
|
|
229
|
+
os.replace(temp_name, path)
|
|
230
|
+
except Exception:
|
|
231
|
+
try:
|
|
232
|
+
os.unlink(temp_name)
|
|
233
|
+
except OSError:
|
|
234
|
+
pass
|
|
235
|
+
raise
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""External interface adapters."""
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Protocol-neutral tool bridge for MCP-compatible hosts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from codecortex.gateway import CodeCortexGateway
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MCPBridge:
|
|
11
|
+
"""Expose stable tool definitions without coupling the core to one transport."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, gateway: CodeCortexGateway) -> None:
|
|
14
|
+
self.gateway = gateway
|
|
15
|
+
|
|
16
|
+
def tool_definitions(self) -> list[dict[str, Any]]:
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
"name": "cortex_route",
|
|
20
|
+
"description": "Classify a coding request and return the selected capabilities.",
|
|
21
|
+
"inputSchema": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"properties": {"query": {"type": "string"}},
|
|
24
|
+
"required": ["query"],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "cortex_query",
|
|
29
|
+
"description": "Run repository intelligence for a coding request.",
|
|
30
|
+
"inputSchema": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {"query": {"type": "string"}},
|
|
33
|
+
"required": ["query"],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "cortex_remember",
|
|
38
|
+
"description": "Save a project decision or reusable fact.",
|
|
39
|
+
"inputSchema": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"properties": {
|
|
42
|
+
"key": {"type": "string"},
|
|
43
|
+
"value": {"type": "string"},
|
|
44
|
+
},
|
|
45
|
+
"required": ["key", "value"],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "cortex_health",
|
|
50
|
+
"description": "Return CodeCortex engine health.",
|
|
51
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
52
|
+
},
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
async def call(self, name: str, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
56
|
+
if name == "cortex_route":
|
|
57
|
+
return self.gateway.route(str(arguments["query"])).model_dump(mode="json")
|
|
58
|
+
if name == "cortex_query":
|
|
59
|
+
result = await self.gateway.query(str(arguments["query"]))
|
|
60
|
+
return result.model_dump(mode="json")
|
|
61
|
+
if name == "cortex_remember":
|
|
62
|
+
await self.gateway.remember(str(arguments["key"]), str(arguments["value"]))
|
|
63
|
+
return {"saved": True}
|
|
64
|
+
if name == "cortex_health":
|
|
65
|
+
return await self.gateway.health()
|
|
66
|
+
raise KeyError(f"Unknown tool: {name}")
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Optional Tree-sitter parser provider for production polyglot structure."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, slots=True)
|
|
10
|
+
class NativeUnit:
|
|
11
|
+
name: str
|
|
12
|
+
kind: str
|
|
13
|
+
line: int
|
|
14
|
+
end_line: int
|
|
15
|
+
signature: str | None = None
|
|
16
|
+
return_type: str | None = None
|
|
17
|
+
bases: tuple[str, ...] = ()
|
|
18
|
+
references: tuple[str, ...] = ()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TreeSitterParserProvider:
|
|
22
|
+
"""Parse major languages with native grammars when the parser extra is installed."""
|
|
23
|
+
|
|
24
|
+
aliases = {
|
|
25
|
+
"typescript": "typescript",
|
|
26
|
+
"javascript": "javascript",
|
|
27
|
+
"go": "go",
|
|
28
|
+
"rust": "rust",
|
|
29
|
+
"java": "java",
|
|
30
|
+
"c": "c",
|
|
31
|
+
"cpp": "cpp",
|
|
32
|
+
"csharp": "csharp",
|
|
33
|
+
"php": "php",
|
|
34
|
+
"ruby": "ruby",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
kinds = {
|
|
38
|
+
"class_declaration": "class",
|
|
39
|
+
"class_definition": "class",
|
|
40
|
+
"class_specifier": "class",
|
|
41
|
+
"interface_declaration": "interface",
|
|
42
|
+
"trait_item": "interface",
|
|
43
|
+
"struct_item": "struct",
|
|
44
|
+
"struct_specifier": "struct",
|
|
45
|
+
"enum_item": "enum",
|
|
46
|
+
"enum_declaration": "enum",
|
|
47
|
+
"function_declaration": "function",
|
|
48
|
+
"function_definition": "function",
|
|
49
|
+
"function_item": "function",
|
|
50
|
+
"method_declaration": "method",
|
|
51
|
+
"method_definition": "method",
|
|
52
|
+
"constructor_declaration": "constructor",
|
|
53
|
+
"singleton_method": "method",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
def __init__(self) -> None:
|
|
57
|
+
try:
|
|
58
|
+
from tree_sitter_language_pack import get_parser
|
|
59
|
+
except ImportError as exc:
|
|
60
|
+
raise RuntimeError("install CodeCortex with the `parsers` extra") from exc
|
|
61
|
+
self._get_parser = get_parser
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def available(cls) -> bool:
|
|
65
|
+
try:
|
|
66
|
+
import tree_sitter_language_pack # noqa: F401
|
|
67
|
+
except ImportError:
|
|
68
|
+
return False
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
def parse(self, language: str, source: str) -> list[NativeUnit]:
|
|
72
|
+
alias = self.aliases.get(language)
|
|
73
|
+
if alias is None:
|
|
74
|
+
return []
|
|
75
|
+
parser = self._get_parser(alias)
|
|
76
|
+
tree = parser.parse(source.encode("utf-8"))
|
|
77
|
+
root = tree.root_node
|
|
78
|
+
source_bytes = source.encode("utf-8")
|
|
79
|
+
units: list[NativeUnit] = []
|
|
80
|
+
stack = [root]
|
|
81
|
+
while stack:
|
|
82
|
+
node = stack.pop()
|
|
83
|
+
stack.extend(reversed(list(getattr(node, "children", ()))))
|
|
84
|
+
kind = self.kinds.get(str(getattr(node, "type", "")))
|
|
85
|
+
if kind is None:
|
|
86
|
+
continue
|
|
87
|
+
name_node = self._field(node, "name") or self._first_identifier(node)
|
|
88
|
+
if name_node is None:
|
|
89
|
+
continue
|
|
90
|
+
name = self._text(name_node, source_bytes).strip()
|
|
91
|
+
if not name:
|
|
92
|
+
continue
|
|
93
|
+
return_node = self._field(node, "return_type") or self._field(node, "type")
|
|
94
|
+
bases = self._bases(node, source_bytes)
|
|
95
|
+
refs = self._references(node, source_bytes, name)
|
|
96
|
+
units.append(
|
|
97
|
+
NativeUnit(
|
|
98
|
+
name=name,
|
|
99
|
+
kind=kind,
|
|
100
|
+
line=int(node.start_point.row) + 1,
|
|
101
|
+
end_line=int(node.end_point.row) + 1,
|
|
102
|
+
signature=self._signature(node, source_bytes),
|
|
103
|
+
return_type=(
|
|
104
|
+
self._text(return_node, source_bytes).strip()
|
|
105
|
+
if return_node is not None
|
|
106
|
+
else None
|
|
107
|
+
),
|
|
108
|
+
bases=bases,
|
|
109
|
+
references=refs,
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
units.sort(key=lambda item: (item.line, item.end_line, item.name))
|
|
113
|
+
return units
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def _field(node: Any, name: str) -> Any | None:
|
|
117
|
+
method = getattr(node, "child_by_field_name", None)
|
|
118
|
+
return method(name) if callable(method) else None
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def _text(node: Any, source: bytes) -> str:
|
|
122
|
+
return source[int(node.start_byte) : int(node.end_byte)].decode("utf-8", errors="replace")
|
|
123
|
+
|
|
124
|
+
def _first_identifier(self, node: Any) -> Any | None:
|
|
125
|
+
stack = list(getattr(node, "children", ()))
|
|
126
|
+
while stack:
|
|
127
|
+
child = stack.pop(0)
|
|
128
|
+
if str(getattr(child, "type", "")) in {
|
|
129
|
+
"identifier",
|
|
130
|
+
"type_identifier",
|
|
131
|
+
"constant",
|
|
132
|
+
"name",
|
|
133
|
+
}:
|
|
134
|
+
return child
|
|
135
|
+
stack[0:0] = list(getattr(child, "children", ()))
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
def _signature(self, node: Any, source: bytes) -> str | None:
|
|
139
|
+
body = self._field(node, "body")
|
|
140
|
+
end = int(body.start_byte) if body is not None else int(node.end_byte)
|
|
141
|
+
text = source[int(node.start_byte) : end].decode("utf-8", errors="replace").strip()
|
|
142
|
+
text = " ".join(text.split())
|
|
143
|
+
return text[:800] or None
|
|
144
|
+
|
|
145
|
+
def _bases(self, node: Any, source: bytes) -> tuple[str, ...]:
|
|
146
|
+
values: list[str] = []
|
|
147
|
+
for field in ("superclass", "interfaces", "base", "type_parameters"):
|
|
148
|
+
child = self._field(node, field)
|
|
149
|
+
if child is not None:
|
|
150
|
+
value = self._text(child, source).strip()
|
|
151
|
+
if value:
|
|
152
|
+
values.append(value)
|
|
153
|
+
return tuple(dict.fromkeys(values))
|
|
154
|
+
|
|
155
|
+
def _references(self, node: Any, source: bytes, own_name: str) -> tuple[str, ...]:
|
|
156
|
+
values: list[str] = []
|
|
157
|
+
stack = list(getattr(node, "children", ()))
|
|
158
|
+
while stack and len(values) < 128:
|
|
159
|
+
child = stack.pop()
|
|
160
|
+
child_type = str(getattr(child, "type", ""))
|
|
161
|
+
if child_type in {"identifier", "type_identifier", "constant"}:
|
|
162
|
+
value = self._text(child, source).strip()
|
|
163
|
+
if value and value != own_name and value not in values:
|
|
164
|
+
values.append(value)
|
|
165
|
+
stack.extend(getattr(child, "children", ()))
|
|
166
|
+
return tuple(values)
|