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
codecortex/cli.py
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"""CodeCortex command-line interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import shlex
|
|
7
|
+
from dataclasses import asdict
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Annotated
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
|
|
15
|
+
from codecortex.architecture import (
|
|
16
|
+
ArchitectureDriftDetector,
|
|
17
|
+
ArchitectureFingerprint,
|
|
18
|
+
ArchitectureInferenceEngine,
|
|
19
|
+
)
|
|
20
|
+
from codecortex.benchmark import BenchmarkSuite, CodeCortexGraphStrategy, FullTextBaseline
|
|
21
|
+
from codecortex.dashboard import run_dashboard
|
|
22
|
+
from codecortex.evaluation import (
|
|
23
|
+
BenchmarkHistory,
|
|
24
|
+
ExternalEvaluationSuite,
|
|
25
|
+
RegressionGate,
|
|
26
|
+
SubprocessEvaluationTarget,
|
|
27
|
+
)
|
|
28
|
+
from codecortex.git_intelligence import GitIntelligence
|
|
29
|
+
from codecortex.indexing.impact import ImpactAnalyzer
|
|
30
|
+
from codecortex.indexing.incremental_graph import IncrementalGraphIndex
|
|
31
|
+
from codecortex.mcp.server import run_stdio
|
|
32
|
+
from codecortex.memory import TeamMemoryStore
|
|
33
|
+
from codecortex.memory.knowledge import ProjectKnowledgeExtractor
|
|
34
|
+
from codecortex.pr_intelligence import PRIntelligence
|
|
35
|
+
from codecortex.retrieval import RepositorySemanticIndex
|
|
36
|
+
from codecortex.runtime import build_runtime
|
|
37
|
+
from codecortex.setup import ProjectSetup
|
|
38
|
+
from codecortex.tracing import TaskTraceRecorder
|
|
39
|
+
from codecortex.workspace import MultiRepositoryWorkspace
|
|
40
|
+
|
|
41
|
+
app = typer.Typer(no_args_is_help=True, add_completion=False)
|
|
42
|
+
console = Console()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _root(path: Path) -> Path:
|
|
46
|
+
return path.expanduser().resolve()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _graph(root: Path):
|
|
50
|
+
return IncrementalGraphIndex(root).refresh()[0]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.command()
|
|
54
|
+
def init(path: Annotated[Path, typer.Argument(help="Project directory")] = Path(".")) -> None:
|
|
55
|
+
result = ProjectSetup(_root(path)).run()
|
|
56
|
+
console.print("[bold green]CodeCortex ready.[/bold green]")
|
|
57
|
+
console.print_json(
|
|
58
|
+
data={
|
|
59
|
+
"tracked_files": result.index.tracked,
|
|
60
|
+
"symbols": result.symbols,
|
|
61
|
+
"graph_nodes": result.graph_nodes,
|
|
62
|
+
"graph_edges": result.graph_edges,
|
|
63
|
+
"languages": list(result.languages),
|
|
64
|
+
"detected_agents": list(result.detected_agents),
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.command("index")
|
|
70
|
+
def index_command(path: Annotated[Path, typer.Option("--path", "-p")] = Path(".")) -> None:
|
|
71
|
+
graph, stats = IncrementalGraphIndex(_root(path)).refresh()
|
|
72
|
+
console.print_json(
|
|
73
|
+
data={
|
|
74
|
+
"tracked": stats.index.tracked,
|
|
75
|
+
"added": len(stats.index.added),
|
|
76
|
+
"changed": len(stats.index.changed),
|
|
77
|
+
"removed": len(stats.index.removed),
|
|
78
|
+
"files_reparsed": stats.files_reparsed,
|
|
79
|
+
"full_rebuild": stats.full_rebuild,
|
|
80
|
+
"graph_nodes": len(graph.nodes),
|
|
81
|
+
"graph_edges": len(graph.edges),
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@app.command()
|
|
87
|
+
def semantic(
|
|
88
|
+
query: Annotated[str, typer.Argument(help="Semantic repository query")],
|
|
89
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
90
|
+
limit: Annotated[int, typer.Option("--limit", "-n")] = 20,
|
|
91
|
+
) -> None:
|
|
92
|
+
semantic_index = RepositorySemanticIndex(_root(path))
|
|
93
|
+
semantic_index.refresh()
|
|
94
|
+
console.print_json(
|
|
95
|
+
data={
|
|
96
|
+
"hits": [
|
|
97
|
+
{
|
|
98
|
+
"id": hit.document.id,
|
|
99
|
+
"score": hit.score,
|
|
100
|
+
"vector_score": hit.vector_score,
|
|
101
|
+
"lexical_score": hit.lexical_score,
|
|
102
|
+
"structural_score": hit.structural_score,
|
|
103
|
+
"metadata": hit.document.metadata,
|
|
104
|
+
}
|
|
105
|
+
for hit in semantic_index.search(query, limit)
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@app.command()
|
|
112
|
+
def architecture(path: Annotated[Path, typer.Option("--path", "-p")] = Path(".")) -> None:
|
|
113
|
+
report = ArchitectureInferenceEngine().analyze(_graph(_root(path)))
|
|
114
|
+
console.print_json(data=asdict(report))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@app.command("architecture-baseline")
|
|
118
|
+
def architecture_baseline(
|
|
119
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
120
|
+
) -> None:
|
|
121
|
+
root = _root(path)
|
|
122
|
+
target = root / ".codecortex" / "architecture" / "baseline.json"
|
|
123
|
+
ArchitectureDriftDetector().fingerprint(_graph(root)).save(target)
|
|
124
|
+
console.print(f"Saved: {target}")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@app.command("architecture-drift")
|
|
128
|
+
def architecture_drift(
|
|
129
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
130
|
+
) -> None:
|
|
131
|
+
root = _root(path)
|
|
132
|
+
target = root / ".codecortex" / "architecture" / "baseline.json"
|
|
133
|
+
baseline = ArchitectureFingerprint.load(target)
|
|
134
|
+
detector = ArchitectureDriftDetector()
|
|
135
|
+
current = detector.fingerprint(_graph(root))
|
|
136
|
+
if baseline is None:
|
|
137
|
+
current.save(target)
|
|
138
|
+
console.print("Baseline created; no prior baseline existed.")
|
|
139
|
+
return
|
|
140
|
+
report = detector.compare(baseline, current)
|
|
141
|
+
console.print_json(data=asdict(report))
|
|
142
|
+
if report.drifted and report.score >= 0.70:
|
|
143
|
+
raise typer.Exit(code=2)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@app.command("symbol-history")
|
|
147
|
+
def symbol_history(
|
|
148
|
+
target: Annotated[str, typer.Argument(help="Repository-relative path")],
|
|
149
|
+
start: Annotated[int, typer.Argument(help="Start line")],
|
|
150
|
+
end: Annotated[int, typer.Argument(help="End line")],
|
|
151
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
152
|
+
) -> None:
|
|
153
|
+
report = GitIntelligence(_root(path)).symbol_history(target, start, end)
|
|
154
|
+
console.print_json(data=asdict(report))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@app.command("pr")
|
|
158
|
+
def pr_command(
|
|
159
|
+
base: Annotated[str, typer.Argument(help="Base Git ref")],
|
|
160
|
+
head: Annotated[str, typer.Option("--head")] = "HEAD",
|
|
161
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
162
|
+
) -> None:
|
|
163
|
+
root = _root(path)
|
|
164
|
+
report = PRIntelligence(root, _graph(root)).analyze(base, head)
|
|
165
|
+
console.print_json(
|
|
166
|
+
data={
|
|
167
|
+
"base_ref": report.base_ref,
|
|
168
|
+
"head_ref": report.head_ref,
|
|
169
|
+
"risk_score": report.risk_score,
|
|
170
|
+
"risk_level": report.risk_level,
|
|
171
|
+
"affected_tests": list(report.affected_tests),
|
|
172
|
+
"files": [asdict(item) for item in report.files],
|
|
173
|
+
"symbols": [
|
|
174
|
+
{
|
|
175
|
+
"node": item.node.model_dump(mode="json"),
|
|
176
|
+
"impact_risk": item.impact_risk,
|
|
177
|
+
"affected_nodes": item.affected_nodes,
|
|
178
|
+
"affected_tests": item.affected_tests,
|
|
179
|
+
}
|
|
180
|
+
for item in report.symbols
|
|
181
|
+
],
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@app.command("team-remember")
|
|
187
|
+
def team_remember(
|
|
188
|
+
key: Annotated[str, typer.Argument()],
|
|
189
|
+
value: Annotated[str, typer.Argument()],
|
|
190
|
+
actor: Annotated[str, typer.Option("--actor")] = "user",
|
|
191
|
+
namespace: Annotated[str, typer.Option("--namespace")] = "project",
|
|
192
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
193
|
+
) -> None:
|
|
194
|
+
store = TeamMemoryStore(_root(path) / ".codecortex" / "memory" / "team.sqlite3")
|
|
195
|
+
entry = store.put_entry(namespace, key, value, actor=actor)
|
|
196
|
+
console.print_json(data=asdict(entry))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@app.command("team-search")
|
|
200
|
+
def team_search(
|
|
201
|
+
query: Annotated[str, typer.Argument()],
|
|
202
|
+
namespace: Annotated[str, typer.Option("--namespace")] = "project",
|
|
203
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
204
|
+
) -> None:
|
|
205
|
+
store = TeamMemoryStore(_root(path) / ".codecortex" / "memory" / "team.sqlite3")
|
|
206
|
+
console.print_json(
|
|
207
|
+
data=[asdict(item) for item in store.search_entries(namespace, query, 20)]
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@app.command("workspace-add")
|
|
212
|
+
def workspace_add(
|
|
213
|
+
name: Annotated[str, typer.Argument()],
|
|
214
|
+
repository: Annotated[Path, typer.Argument()],
|
|
215
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
216
|
+
) -> None:
|
|
217
|
+
workspace = MultiRepositoryWorkspace(_root(path) / ".codecortex" / "workspace.json")
|
|
218
|
+
workspace.add_repository(name, repository)
|
|
219
|
+
console.print(f"Added {name}: {_root(repository)}")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@app.command("workspace-search")
|
|
223
|
+
def workspace_search(
|
|
224
|
+
query: Annotated[str, typer.Argument()],
|
|
225
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
226
|
+
) -> None:
|
|
227
|
+
workspace = MultiRepositoryWorkspace(_root(path) / ".codecortex" / "workspace.json")
|
|
228
|
+
console.print_json(
|
|
229
|
+
data=[
|
|
230
|
+
{
|
|
231
|
+
"repository": hit.repository,
|
|
232
|
+
"score": hit.score,
|
|
233
|
+
"node": hit.node.model_dump(mode="json"),
|
|
234
|
+
}
|
|
235
|
+
for hit in workspace.search(query)
|
|
236
|
+
]
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@app.command("trace-summary")
|
|
241
|
+
def trace_summary(
|
|
242
|
+
trace_id: Annotated[str, typer.Argument()],
|
|
243
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
244
|
+
) -> None:
|
|
245
|
+
recorder = TaskTraceRecorder(_root(path) / ".codecortex" / "runtime" / "traces.jsonl")
|
|
246
|
+
console.print_json(data=asdict(recorder.summarize(trace_id)))
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@app.command()
|
|
250
|
+
def impact(
|
|
251
|
+
query: Annotated[str, typer.Argument(help="Symbol or file")],
|
|
252
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
253
|
+
) -> None:
|
|
254
|
+
console.print(ImpactAnalyzer(_graph(_root(path))).analyze(query).to_text())
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@app.command()
|
|
258
|
+
def history(
|
|
259
|
+
target: Annotated[str, typer.Argument(help="Repository path")],
|
|
260
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
261
|
+
) -> None:
|
|
262
|
+
console.print_json(data=GitIntelligence(_root(path)).file_history(target))
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@app.command()
|
|
266
|
+
def knowledge(path: Annotated[Path, typer.Option("--path", "-p")] = Path(".")) -> None:
|
|
267
|
+
console.print_json(data=ProjectKnowledgeExtractor(_root(path)).extract().facts())
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@app.command()
|
|
271
|
+
def mcp(path: Annotated[Path, typer.Option("--path", "-p")] = Path(".")) -> None:
|
|
272
|
+
run_stdio(_root(path))
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@app.command()
|
|
276
|
+
def benchmark(
|
|
277
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
278
|
+
cases: Annotated[Path, typer.Option("--cases")] = Path("benchmarks/cases.json"),
|
|
279
|
+
output: Annotated[Path, typer.Option("--output", "-o")] = Path("benchmarks/results.json"),
|
|
280
|
+
) -> None:
|
|
281
|
+
root = _root(path)
|
|
282
|
+
case_path = cases if cases.is_absolute() else root / cases
|
|
283
|
+
output_path = output if output.is_absolute() else root / output
|
|
284
|
+
suite = BenchmarkSuite.load(
|
|
285
|
+
case_path,
|
|
286
|
+
[FullTextBaseline(root), CodeCortexGraphStrategy(root)],
|
|
287
|
+
)
|
|
288
|
+
report = suite.run()
|
|
289
|
+
report.save(output_path)
|
|
290
|
+
history_store = BenchmarkHistory(root / ".codecortex" / "benchmarks" / "history.json")
|
|
291
|
+
history_store.append(report.summary())
|
|
292
|
+
console.print_json(data=report.summary())
|
|
293
|
+
console.print(f"Saved: {output_path}")
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@app.command("benchmark-gate")
|
|
297
|
+
def benchmark_gate(
|
|
298
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
299
|
+
) -> None:
|
|
300
|
+
snapshots = BenchmarkHistory(
|
|
301
|
+
_root(path) / ".codecortex" / "benchmarks" / "history.json"
|
|
302
|
+
).load()
|
|
303
|
+
if len(snapshots) < 2:
|
|
304
|
+
console.print("At least two benchmark snapshots are required.")
|
|
305
|
+
return
|
|
306
|
+
report = RegressionGate().evaluate(snapshots[-1], snapshots[-2])
|
|
307
|
+
console.print_json(data=asdict(report))
|
|
308
|
+
if not report.passed:
|
|
309
|
+
raise typer.Exit(code=2)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@app.command("evaluate")
|
|
313
|
+
def evaluate_command(
|
|
314
|
+
suite: Annotated[Path, typer.Argument(help="Evaluation suite JSON")],
|
|
315
|
+
command: Annotated[str, typer.Argument(help="Quoted external target command")],
|
|
316
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
317
|
+
output: Annotated[Path, typer.Option("--output", "-o")] = Path(
|
|
318
|
+
"benchmarks/external/results.json"
|
|
319
|
+
),
|
|
320
|
+
) -> None:
|
|
321
|
+
root = _root(path)
|
|
322
|
+
suite_path = suite if suite.is_absolute() else root / suite
|
|
323
|
+
output_path = output if output.is_absolute() else root / output
|
|
324
|
+
target = SubprocessEvaluationTarget("external", tuple(shlex.split(command)), cwd=root)
|
|
325
|
+
report = asyncio.run(ExternalEvaluationSuite.load(suite_path).run(target))
|
|
326
|
+
report.save(output_path)
|
|
327
|
+
console.print_json(data=report.summary())
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@app.command()
|
|
331
|
+
def doctor(path: Annotated[Path, typer.Option("--path", "-p")] = Path(".")) -> None:
|
|
332
|
+
runtime = build_runtime(_root(path))
|
|
333
|
+
health = asyncio.run(runtime.gateway.health())
|
|
334
|
+
table = Table(title="CodeCortex Health")
|
|
335
|
+
table.add_column("Capability")
|
|
336
|
+
table.add_column("Status")
|
|
337
|
+
for capability, status in health.items():
|
|
338
|
+
table.add_row(capability, "OK" if status else "Unavailable")
|
|
339
|
+
console.print(table)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@app.command()
|
|
343
|
+
def route(
|
|
344
|
+
query: Annotated[str, typer.Argument(help="Coding request")],
|
|
345
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
346
|
+
) -> None:
|
|
347
|
+
runtime = build_runtime(_root(path))
|
|
348
|
+
plan = runtime.gateway.route(query, str(runtime.config.project_root))
|
|
349
|
+
console.print_json(data=plan.model_dump(mode="json"))
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@app.command()
|
|
353
|
+
def run(
|
|
354
|
+
query: Annotated[str, typer.Argument(help="Coding request")],
|
|
355
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
356
|
+
) -> None:
|
|
357
|
+
runtime = build_runtime(_root(path))
|
|
358
|
+
result = asyncio.run(runtime.gateway.query(query, str(runtime.config.project_root)))
|
|
359
|
+
console.print(f"[bold]Route:[/bold] {', '.join(item.value for item in result.plan.selected)}")
|
|
360
|
+
console.print(f"[bold]Context:[/bold] {result.context_tokens}/{result.plan.context_budget} tokens")
|
|
361
|
+
if trace_id := result.metadata.get("trace_id"):
|
|
362
|
+
console.print(f"[bold]Trace:[/bold] {trace_id}")
|
|
363
|
+
for engine_result in result.results:
|
|
364
|
+
console.rule(engine_result.capability.value)
|
|
365
|
+
console.print(engine_result.content)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@app.command()
|
|
369
|
+
def remember(
|
|
370
|
+
key: Annotated[str, typer.Argument()],
|
|
371
|
+
value: Annotated[str, typer.Argument()],
|
|
372
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
373
|
+
) -> None:
|
|
374
|
+
runtime = build_runtime(_root(path))
|
|
375
|
+
asyncio.run(runtime.gateway.remember(key, value))
|
|
376
|
+
console.print("[green]Saved.[/green]")
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@app.command()
|
|
380
|
+
def stats(path: Annotated[Path, typer.Option("--path", "-p")] = Path(".")) -> None:
|
|
381
|
+
root = _root(path)
|
|
382
|
+
runtime = build_runtime(root)
|
|
383
|
+
graph, graph_stats = IncrementalGraphIndex(root).refresh()
|
|
384
|
+
git = GitIntelligence(root).analyze(300)
|
|
385
|
+
console.print_json(
|
|
386
|
+
data={
|
|
387
|
+
"files": graph_stats.index.tracked,
|
|
388
|
+
"graph_nodes": len(graph.nodes),
|
|
389
|
+
"graph_edges": len(graph.edges),
|
|
390
|
+
"graph_counts": graph.counts(),
|
|
391
|
+
"git_commits": git.commits,
|
|
392
|
+
"health": asyncio.run(runtime.gateway.health()),
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@app.command()
|
|
398
|
+
def dashboard(
|
|
399
|
+
path: Annotated[Path, typer.Option("--path", "-p")] = Path("."),
|
|
400
|
+
host: Annotated[str, typer.Option("--host")] = "127.0.0.1",
|
|
401
|
+
port: Annotated[int, typer.Option("--port")] = 7331,
|
|
402
|
+
) -> None:
|
|
403
|
+
runtime = build_runtime(_root(path))
|
|
404
|
+
console.print(f"Dashboard: http://{host}:{port}")
|
|
405
|
+
run_dashboard(runtime, host=host, port=port)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
if __name__ == "__main__":
|
|
409
|
+
app()
|
codecortex/config.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Configuration for CodeCortex."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CortexConfig(BaseModel):
|
|
11
|
+
project_root: Path = Field(default_factory=lambda: Path.cwd())
|
|
12
|
+
state_dir_name: str = ".codecortex"
|
|
13
|
+
default_context_budget: int = Field(default=32_000, gt=0)
|
|
14
|
+
hard_context_limit: int = Field(default=128_000, gt=0)
|
|
15
|
+
telemetry_enabled: bool = True
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def state_dir(self) -> Path:
|
|
19
|
+
return self.project_root / self.state_dir_name
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def memory_dir(self) -> Path:
|
|
23
|
+
return self.state_dir / "memory"
|
|
24
|
+
|
|
25
|
+
def ensure_directories(self) -> None:
|
|
26
|
+
self.state_dir.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
self.memory_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Context processing and token budgeting."""
|
|
2
|
+
|
|
3
|
+
from codecortex.context.budget import BudgetContextProcessor
|
|
4
|
+
from codecortex.context.integrated import IntegratedContextProcessor
|
|
5
|
+
from codecortex.context.pipeline import ContextMetrics, ContextPipeline, ContextResult
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"BudgetContextProcessor",
|
|
9
|
+
"ContextMetrics",
|
|
10
|
+
"ContextPipeline",
|
|
11
|
+
"ContextResult",
|
|
12
|
+
"IntegratedContextProcessor",
|
|
13
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Token-budget aware context processing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from codecortex.core.contracts import ContextProcessor
|
|
6
|
+
from codecortex.core.models import ContextChunk
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BudgetContextProcessor(ContextProcessor):
|
|
10
|
+
"""Deduplicate, rank, and fit useful context inside a token budget."""
|
|
11
|
+
|
|
12
|
+
async def fit(self, chunks: list[ContextChunk], budget: int) -> list[ContextChunk]:
|
|
13
|
+
unique = self._deduplicate(chunks)
|
|
14
|
+
ranked = sorted(
|
|
15
|
+
unique,
|
|
16
|
+
key=lambda chunk: (chunk.relevance, -chunk.tokens),
|
|
17
|
+
reverse=True,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
selected: list[ContextChunk] = []
|
|
21
|
+
remaining = budget
|
|
22
|
+
for chunk in ranked:
|
|
23
|
+
if remaining <= 0:
|
|
24
|
+
break
|
|
25
|
+
if chunk.tokens <= remaining:
|
|
26
|
+
selected.append(chunk)
|
|
27
|
+
remaining -= chunk.tokens
|
|
28
|
+
continue
|
|
29
|
+
if remaining < 32:
|
|
30
|
+
continue
|
|
31
|
+
selected.append(self._truncate(chunk, remaining))
|
|
32
|
+
remaining = 0
|
|
33
|
+
return selected
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def _deduplicate(chunks: list[ContextChunk]) -> list[ContextChunk]:
|
|
37
|
+
by_content: dict[str, ContextChunk] = {}
|
|
38
|
+
for chunk in chunks:
|
|
39
|
+
key = " ".join(chunk.content.split())
|
|
40
|
+
current = by_content.get(key)
|
|
41
|
+
if current is None or chunk.relevance > current.relevance:
|
|
42
|
+
by_content[key] = chunk
|
|
43
|
+
return list(by_content.values())
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def _truncate(chunk: ContextChunk, tokens: int) -> ContextChunk:
|
|
47
|
+
char_limit = max(1, tokens * 4)
|
|
48
|
+
content = chunk.content[:char_limit].rstrip()
|
|
49
|
+
metadata = dict(chunk.metadata)
|
|
50
|
+
metadata.update(
|
|
51
|
+
{
|
|
52
|
+
"truncated": True,
|
|
53
|
+
"original_tokens": chunk.tokens,
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
return chunk.model_copy(
|
|
57
|
+
update={
|
|
58
|
+
"content": content,
|
|
59
|
+
"tokens": tokens,
|
|
60
|
+
"metadata": metadata,
|
|
61
|
+
}
|
|
62
|
+
)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Context processor that opportunistically uses the mature compression backend."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
from codecortex.backends.context import ContextBackendAdapter
|
|
8
|
+
from codecortex.backends.mcp_client import MCPStdioClient
|
|
9
|
+
from codecortex.context.budget import BudgetContextProcessor
|
|
10
|
+
from codecortex.core.contracts import ContextProcessor
|
|
11
|
+
from codecortex.core.models import ContextChunk
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class IntegratedContextProcessor(ContextProcessor):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
backend: ContextBackendAdapter | None = None,
|
|
18
|
+
*,
|
|
19
|
+
compression_threshold: int = 512,
|
|
20
|
+
) -> None:
|
|
21
|
+
self.backend = backend
|
|
22
|
+
self.compression_threshold = compression_threshold
|
|
23
|
+
self.budget = BudgetContextProcessor()
|
|
24
|
+
|
|
25
|
+
async def fit(self, chunks: list[ContextChunk], budget: int) -> list[ContextChunk]:
|
|
26
|
+
backend = self.backend
|
|
27
|
+
if backend is None or not await backend.health():
|
|
28
|
+
return await self.budget.fit(chunks, budget)
|
|
29
|
+
candidates = [chunk for chunk in chunks if chunk.tokens >= self.compression_threshold]
|
|
30
|
+
if not candidates:
|
|
31
|
+
return await self.budget.fit(chunks, budget)
|
|
32
|
+
try:
|
|
33
|
+
payloads = await asyncio.to_thread(
|
|
34
|
+
backend.compress_batch,
|
|
35
|
+
[chunk.content for chunk in candidates],
|
|
36
|
+
)
|
|
37
|
+
except Exception:
|
|
38
|
+
return await self.budget.fit(chunks, budget)
|
|
39
|
+
replacements: dict[int, ContextChunk] = {}
|
|
40
|
+
for chunk, payload in zip(candidates, payloads, strict=True):
|
|
41
|
+
content = MCPStdioClient.content_text(payload).strip()
|
|
42
|
+
if not content:
|
|
43
|
+
continue
|
|
44
|
+
compressed_tokens = max(1, len(content) // 4)
|
|
45
|
+
if compressed_tokens >= chunk.tokens:
|
|
46
|
+
continue
|
|
47
|
+
metadata = dict(chunk.metadata)
|
|
48
|
+
metadata.update(
|
|
49
|
+
{
|
|
50
|
+
"compressed": True,
|
|
51
|
+
"original_tokens": chunk.tokens,
|
|
52
|
+
"compression_backend": backend.spec.key,
|
|
53
|
+
"compression_revision": backend.spec.revision,
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
replacements[id(chunk)] = chunk.model_copy(
|
|
57
|
+
update={"content": content, "tokens": compressed_tokens, "metadata": metadata}
|
|
58
|
+
)
|
|
59
|
+
normalized = [replacements.get(id(chunk), chunk) for chunk in chunks]
|
|
60
|
+
return await self.budget.fit(normalized, budget)
|