kinetic-context 0.2.1__tar.gz
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.
- kinetic_context-0.2.1/LICENSE +21 -0
- kinetic_context-0.2.1/PKG-INFO +348 -0
- kinetic_context-0.2.1/README.md +308 -0
- kinetic_context-0.2.1/kce/__init__.py +5 -0
- kinetic_context-0.2.1/kce/_version.py +1 -0
- kinetic_context-0.2.1/kce/assembly/__init__.py +2 -0
- kinetic_context-0.2.1/kce/assembly/context_builder.py +41 -0
- kinetic_context-0.2.1/kce/benchmark/__init__.py +2 -0
- kinetic_context-0.2.1/kce/benchmark/dataset.py +334 -0
- kinetic_context-0.2.1/kce/benchmark/django_dataset.py +42 -0
- kinetic_context-0.2.1/kce/benchmark/flask_dataset.py +42 -0
- kinetic_context-0.2.1/kce/benchmark/metrics.py +26 -0
- kinetic_context-0.2.1/kce/benchmark/runner.py +275 -0
- kinetic_context-0.2.1/kce/cli.py +568 -0
- kinetic_context-0.2.1/kce/config.py +120 -0
- kinetic_context-0.2.1/kce/coordinator/__init__.py +2 -0
- kinetic_context-0.2.1/kce/coordinator/intent.py +95 -0
- kinetic_context-0.2.1/kce/coordinator/query_coordinator.py +59 -0
- kinetic_context-0.2.1/kce/coordinator/query_expansion.py +68 -0
- kinetic_context-0.2.1/kce/coordinator/query_transform.py +41 -0
- kinetic_context-0.2.1/kce/coordinator/symbol_lookup.py +32 -0
- kinetic_context-0.2.1/kce/embeddings/__init__.py +2 -0
- kinetic_context-0.2.1/kce/embeddings/codestral.py +73 -0
- kinetic_context-0.2.1/kce/engine.py +419 -0
- kinetic_context-0.2.1/kce/graph/__init__.py +2 -0
- kinetic_context-0.2.1/kce/graph/ckg.py +136 -0
- kinetic_context-0.2.1/kce/incremental/__init__.py +2 -0
- kinetic_context-0.2.1/kce/incremental/time_travel.py +145 -0
- kinetic_context-0.2.1/kce/incremental/update.py +47 -0
- kinetic_context-0.2.1/kce/ingestion/__init__.py +2 -0
- kinetic_context-0.2.1/kce/ingestion/chunker.py +109 -0
- kinetic_context-0.2.1/kce/ingestion/discovery.py +51 -0
- kinetic_context-0.2.1/kce/ingestion/parser.py +121 -0
- kinetic_context-0.2.1/kce/ingestion/summarizer.py +78 -0
- kinetic_context-0.2.1/kce/mcp_server.py +321 -0
- kinetic_context-0.2.1/kce/neuro_symbolic/__init__.py +2 -0
- kinetic_context-0.2.1/kce/neuro_symbolic/loop.py +24 -0
- kinetic_context-0.2.1/kce/ranking/__init__.py +2 -0
- kinetic_context-0.2.1/kce/ranking/reranker.py +58 -0
- kinetic_context-0.2.1/kce/ranking/unified_reranker.py +104 -0
- kinetic_context-0.2.1/kce/retrieval/__init__.py +2 -0
- kinetic_context-0.2.1/kce/retrieval/bm25.py +44 -0
- kinetic_context-0.2.1/kce/retrieval/dense.py +40 -0
- kinetic_context-0.2.1/kce/retrieval/graph_retrieval.py +70 -0
- kinetic_context-0.2.1/kce/retrieval/novel_signals.py +221 -0
- kinetic_context-0.2.1/kce/retrieval/rrf.py +18 -0
- kinetic_context-0.2.1/kce/store/__init__.py +2 -0
- kinetic_context-0.2.1/kce/store/index_store.py +57 -0
- kinetic_context-0.2.1/kce/store/registry.py +256 -0
- kinetic_context-0.2.1/kce/store/vector_store.py +141 -0
- kinetic_context-0.2.1/kinetic_context.egg-info/PKG-INFO +348 -0
- kinetic_context-0.2.1/kinetic_context.egg-info/SOURCES.txt +56 -0
- kinetic_context-0.2.1/kinetic_context.egg-info/dependency_links.txt +1 -0
- kinetic_context-0.2.1/kinetic_context.egg-info/entry_points.txt +3 -0
- kinetic_context-0.2.1/kinetic_context.egg-info/requires.txt +13 -0
- kinetic_context-0.2.1/kinetic_context.egg-info/top_level.txt +1 -0
- kinetic_context-0.2.1/pyproject.toml +68 -0
- kinetic_context-0.2.1/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Z.ai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kinetic-context
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Repository-level code context engine — find the right code, fast.
|
|
5
|
+
Author: Z.ai
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/zai-org/kinetic-context
|
|
8
|
+
Project-URL: Repository, https://github.com/zai-org/kinetic-context
|
|
9
|
+
Project-URL: Issues, https://github.com/zai-org/kinetic-context/issues
|
|
10
|
+
Keywords: code,search,context,retrieval,embeddings,ast,tree-sitter,mcp,model-context-protocol,code-intelligence
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
22
|
+
Classifier: Topic :: Text Processing :: Indexing
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: tree-sitter>=0.23
|
|
27
|
+
Requires-Dist: tree-sitter-python>=0.23
|
|
28
|
+
Requires-Dist: tree-sitter-javascript>=0.23
|
|
29
|
+
Requires-Dist: tree-sitter-typescript>=0.23
|
|
30
|
+
Requires-Dist: tree-sitter-go>=0.23
|
|
31
|
+
Requires-Dist: tree-sitter-rust>=0.23
|
|
32
|
+
Requires-Dist: tree-sitter-java>=0.23
|
|
33
|
+
Requires-Dist: rank-bm25>=0.2.2
|
|
34
|
+
Requires-Dist: networkx>=3.0
|
|
35
|
+
Requires-Dist: numpy>=1.24
|
|
36
|
+
Requires-Dist: requests>=2.31
|
|
37
|
+
Requires-Dist: rich>=13.0
|
|
38
|
+
Requires-Dist: tqdm>=4.66
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# kinetic-context
|
|
42
|
+
|
|
43
|
+
> Repository-level code context engine — find the right code, fast.
|
|
44
|
+
|
|
45
|
+
`kinetic-context` is a self-contained, installable code context engine. Point it at any repository and it builds a multi-layer index (AST chunks + embeddings + a code knowledge graph + BM25) that you can query with natural language or identifiers. It returns **code blocks with line ranges** — not just file paths — ready to paste into an LLM prompt.
|
|
46
|
+
|
|
47
|
+
It is designed to be the context layer for coding agents. It ships with a Rich CLI, an MCP server (so Claude Code, Cursor, Continue, and Zed can mount it natively), a TCP JSON mode for any other agent, and a Python library.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Highlights
|
|
52
|
+
|
|
53
|
+
- **Multi-language**: Python, JavaScript, TypeScript, Go, Rust, Java — via tree-sitter.
|
|
54
|
+
- **Structure-aware chunking** (`cAST`): never splits a function mid-body. 35% overlap. Hierarchical summaries at file and repository level.
|
|
55
|
+
- **Hybrid retrieval**: dense (Mistral Codestral Embed, 1536-dim) + BM25 (code-aware tokenizer that splits camelCase, snake_case, and dotted identifiers) + a Code Knowledge Graph with 8 relationship types.
|
|
56
|
+
- **Reciprocal Rank Fusion** with brute-force-optimized weights across 5 channels (dense, BM25, graph, PRF, patch-reverse-engineering).
|
|
57
|
+
- **Zerank-2 reranker** with instruction-following via XML tags, tuned per query intent.
|
|
58
|
+
- **Novel signals** invented for this engine:
|
|
59
|
+
- **Cross-resolution resonance** — L2+L3+L4 consensus boost
|
|
60
|
+
- **Code DNA fingerprinting** — structural similarity (param count, complexity, call count)
|
|
61
|
+
- **Semantic bridges** — virtual graph edges between similar functions
|
|
62
|
+
- **File cohort memory** — files that co-occur in correct answers get boosted across queries
|
|
63
|
+
- **Score distribution shape analysis** — adaptive cutoff detects bimodal / uniform / power-law score distributions
|
|
64
|
+
- **Score gap amplification** — sigmoid sharpening of close scores
|
|
65
|
+
- **Post-rerank filename tie-breaker** — the fix for "correct directory, wrong file" at scale
|
|
66
|
+
- **Adversarial anti-centroid** — penalty for chunks near the worst BM25 hits
|
|
67
|
+
- **Query-to-patch reverse engineering** — generate a hypothetical fix, embed it as a 5th RRF channel
|
|
68
|
+
- **Incremental indexing** with SHA-256 Merkle root hashing. Only changed files are re-embedded. Subsequent `index` runs are fast.
|
|
69
|
+
- **Per-repo isolated storage** under `~/.kinetic/<slug>_<hash>/`. Same-name folders in different parents do not collide. A `manifest.json` records the source path, root hash, file count, and last-indexed timestamp so `kinetic status` can answer "reindex needed?" in <50ms without spinning up the engine.
|
|
70
|
+
- **Hookable everywhere**: MCP server (stdio JSON-RPC), TCP JSON, Python library, Rich CLI.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Install
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install kinetic-context
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Requires Python 3.10+. All heavy dependencies (tree-sitter, numpy, networkx, rich) are bundled — no Qdrant/Pinecone/Milvus/Postgres to install.
|
|
81
|
+
|
|
82
|
+
Set your API keys (a Mistral key for embeddings + a Zerank key for reranking):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
export MISTRAL_API_KEY=...
|
|
86
|
+
export ZEROENTROPY_API_KEY=...
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
You can also override the embedder / reranker URLs and model IDs in `KCEConfig` if you want to point at a different provider.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Quick start
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Index a repo (first run; takes a few minutes for a 1k-file repo)
|
|
97
|
+
kinetic index ./my-repo
|
|
98
|
+
|
|
99
|
+
# Subsequent runs only re-embed files whose SHA-256 hash changed
|
|
100
|
+
kinetic index ./my-repo
|
|
101
|
+
|
|
102
|
+
# Search — returns code blocks with line ranges, not just file paths
|
|
103
|
+
kinetic query "authentication middleware" --code
|
|
104
|
+
|
|
105
|
+
# JSON output (for agent hooks / piping)
|
|
106
|
+
kinetic query "how does routing work" --json | jq '.results[0]'
|
|
107
|
+
|
|
108
|
+
# Check if the index is up to date
|
|
109
|
+
kinetic status ./my-repo
|
|
110
|
+
|
|
111
|
+
# List all indexed repos
|
|
112
|
+
kinetic list
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Query output example
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
────────────────────────────────────────────────────────────────────────
|
|
119
|
+
kinetic-context • semantic_question • zerank • 68ms
|
|
120
|
+
────────────────────────────────────────────────────────────────────────
|
|
121
|
+
Query: how does routing work in flask
|
|
122
|
+
────────────────────────────────────────────────────────────────────────
|
|
123
|
+
Found 8 code blocks across 8 files
|
|
124
|
+
|
|
125
|
+
#1 src/flask/sansio/app.py L240-262 (23 lines) • add_url_rule (function) • python
|
|
126
|
+
def add_url_rule(self, rule, endpoint=None, view_func=None, ...)
|
|
127
|
+
240 │ def add_url_rule(
|
|
128
|
+
241 │ self,
|
|
129
|
+
242 │ rule: str,
|
|
130
|
+
243 │ endpoint: str | None = None,
|
|
131
|
+
...
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Hooking into coding agents
|
|
137
|
+
|
|
138
|
+
### MCP server (Claude Code, Cursor, Continue, Zed, anything MCP-aware)
|
|
139
|
+
|
|
140
|
+
Start the MCP server in the background:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
kinetic mcp
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Or add it to your agent's MCP config:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"mcpServers": {
|
|
151
|
+
"kinetic": {
|
|
152
|
+
"command": "kinetic",
|
|
153
|
+
"args": ["mcp"]
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Four tools are exposed:
|
|
160
|
+
|
|
161
|
+
| Tool | Description |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `kinetic_index` | Index (or incrementally update) a repo |
|
|
164
|
+
| `kinetic_query` | Search the indexed codebase, returns code blocks with line ranges |
|
|
165
|
+
| `kinetic_status` | Check whether the index is up to date |
|
|
166
|
+
| `kinetic_list_indexes` | List all indexed repos |
|
|
167
|
+
|
|
168
|
+
### TCP JSON server (any agent)
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
kinetic serve --port 7878
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Send a single-line JSON request, get a single-line JSON response:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
echo '{"query": "session cookie signing", "top": 5}' | nc localhost 7878
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Python library
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from kce.engine import KCEEngine
|
|
184
|
+
from kce.config import KCEConfig
|
|
185
|
+
from kce.store.registry import Registry
|
|
186
|
+
|
|
187
|
+
cfg = KCEConfig()
|
|
188
|
+
cfg.index_dir = Registry().resolve("/path/to/repo")
|
|
189
|
+
cfg.ensure_dirs()
|
|
190
|
+
engine = KCEEngine(cfg)
|
|
191
|
+
engine.index("/path/to/repo")
|
|
192
|
+
|
|
193
|
+
result = engine.query("how does authentication work")
|
|
194
|
+
for cid in result.final_chunk_ids[:10]:
|
|
195
|
+
chunk = engine.chunk_index[cid]
|
|
196
|
+
print(f"{chunk.rel_path}:{chunk.start_line}-{chunk.end_line} {chunk.name}")
|
|
197
|
+
print(chunk.content)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Storage layout
|
|
203
|
+
|
|
204
|
+
Every indexed repo gets its own directory under `~/.kinetic/`:
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
~/.kinetic/
|
|
208
|
+
flask_f9cbd6f6/ <- slug = name + short hash of abspath
|
|
209
|
+
manifest.json <- source path, root hash, file count, last indexed
|
|
210
|
+
chunks.jsonl <- one CodeChunk per line
|
|
211
|
+
embeddings.npy <- (N, 1536) float32 matrix
|
|
212
|
+
embeddings_ids.json <- row order
|
|
213
|
+
ckg.graphml <- Code Knowledge Graph
|
|
214
|
+
incremental_state.json <- per-file SHA-256 hashes
|
|
215
|
+
change_log.jsonl <- append-only change history
|
|
216
|
+
embed_cache/embeddings.db <- Mistral API cache (SQLite)
|
|
217
|
+
zerank_cache/rerank.db <- Zerank API cache (SQLite)
|
|
218
|
+
file_summaries.json
|
|
219
|
+
repo_summary.txt
|
|
220
|
+
django_ea20f8f1/
|
|
221
|
+
...
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Why per-repo isolation?
|
|
225
|
+
|
|
226
|
+
- Two repos with the same folder name (e.g. `~/work/api` and `~/side/api`) get different slugs because the slug includes a short hash of the absolute path. No collisions.
|
|
227
|
+
- Caches live next to the index, so deleting a repo's index (`kinetic forget`) also frees its cache.
|
|
228
|
+
- The `manifest.json` records a Merkle root hash over all file content hashes. Recomputing this on startup is <50ms even for a 1k-file repo, so `kinetic status` is instant.
|
|
229
|
+
|
|
230
|
+
### Efficient incremental updates
|
|
231
|
+
|
|
232
|
+
1. On `kinetic index <repo>`, we first compute the current Merkle root from disk.
|
|
233
|
+
2. We compare to the stored root in `manifest.json`. If they match, the index is up to date — we're done in <100ms.
|
|
234
|
+
3. If they differ, we walk the per-file SHA-256 hashes in `incremental_state.json` and identify exactly which files were added, modified, or removed.
|
|
235
|
+
4. Only those files are re-parsed, re-summarized, and re-embedded. Existing chunks for unchanged files are reused.
|
|
236
|
+
5. The Mistral embed cache (SQLite, keyed by SHA-256 of the embed text) means even a chunk whose text didn't change but whose `chunk_id` was regenerated will not trigger an API call.
|
|
237
|
+
|
|
238
|
+
For a 1k-file repo where 5 files changed, a re-index takes seconds, not minutes.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Architecture
|
|
243
|
+
|
|
244
|
+

|
|
245
|
+
|
|
246
|
+
The pipeline has four layers:
|
|
247
|
+
|
|
248
|
+
1. **Ingestion** — tree-sitter parses each source file into an AST. The `cAST` chunker walks the AST and produces chunks that respect syntactic boundaries (a function is never split mid-body). Each chunk is enriched with its signature, docstring, decorators, and scope. A Code Knowledge Graph (NetworkX) is built with 8 relationship types: `CALLS`, `INHERITS`, `IMPLEMENTS`, `IMPORTS`, `CONTAINS`, `USES_TYPE`, `OVERRIDES`, `DEPENDS_ON`.
|
|
249
|
+
|
|
250
|
+
2. **Storage** — chunks go to JSONL, embeddings go to a single numpy matrix on disk (+ L2-normalized in memory for fast cosine), the graph goes to GraphML. Each repo gets its own directory under `~/.kinetic/`. SHA-256 Merkle root hashing drives incremental updates.
|
|
251
|
+
|
|
252
|
+
3. **Retrieval & Ranking** — the query coordinator picks one of 5 query types (identifier lookup, semantic question, code completion, bug diagnosis, architecture query). For each type, it applies intent-aware boosts (source vs test vs config files), runs multi-query BM25 + dense + graph retrieval, fuses the results with weighted Reciprocal Rank Fusion, applies novel signals (resonance, DNA, semantic bridges, cohort memory), then reranks the top candidates with Zerank-2.
|
|
253
|
+
|
|
254
|
+
4. **Output** — the final ranked chunks are returned as code blocks with line ranges, signature, and docstring. They can be rendered by the Rich CLI, serialized to JSON, or shipped over MCP / TCP to any coding agent.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Benchmarks
|
|
259
|
+
|
|
260
|
+
We benchmark on two real-world repos with hand-curated query sets. We report Context F1@10, Recall@10, Precision@10, and end-to-end latency. **We deliberately do not compare to other context engines** — those numbers are easy to get wrong and we'd rather show our own results honestly than risk an unfair comparison.
|
|
261
|
+
|
|
262
|
+
### Aggregate metrics
|
|
263
|
+
|
|
264
|
+

|
|
265
|
+
|
|
266
|
+
### Per-query F1 (sorted, so the worst queries are at the top)
|
|
267
|
+
|
|
268
|
+

|
|
269
|
+
|
|
270
|
+
### Query outcome distribution (perfect / partial / failed)
|
|
271
|
+
|
|
272
|
+

|
|
273
|
+
|
|
274
|
+
### Latency distribution
|
|
275
|
+
|
|
276
|
+

|
|
277
|
+
|
|
278
|
+
### Quality vs repository scale
|
|
279
|
+
|
|
280
|
+

|
|
281
|
+
|
|
282
|
+
### Numbers
|
|
283
|
+
|
|
284
|
+
| Corpus | Files | Chunks | Graph (nodes/edges) | Queries | F1@10 | Recall@10 | Precision@10 | Avg latency |
|
|
285
|
+
|---|---:|---:|---:|---:|---:|---:|---:|---:|
|
|
286
|
+
| Flask | 83 | 1,382 | 1,594 / 7,906 | 30 | 0.659 | 0.833 | 0.599 | 68 ms |
|
|
287
|
+
| Django (core) | 308 | 7,207 | 6,574 / 46,408 | 30 | 0.647 | 0.867 | 0.559 | 433 ms |
|
|
288
|
+
|
|
289
|
+
The Django benchmark uses the core Django packages (`django/db/`, `django/http/`, `django/urls/`, `django/template/`, `django/forms/`, `django/core/`, `django/contrib/auth/`, `django/contrib/sessions/`) — the parts of Django that real coding agents actually search. The full Django repo includes 3,000+ files of migrations, tests, and docs that bloat the index without improving retrieval quality on real-world queries.
|
|
290
|
+
|
|
291
|
+
Run the benchmarks yourself:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
git clone https://github.com/pallets/flask /tmp/flask
|
|
295
|
+
git clone https://github.com/django/django /tmp/django
|
|
296
|
+
kinetic index /tmp/flask
|
|
297
|
+
kinetic index /tmp/django
|
|
298
|
+
python scripts/run_bench.py flask
|
|
299
|
+
python scripts/run_bench.py django
|
|
300
|
+
python scripts/gen_charts.py
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Configuration
|
|
306
|
+
|
|
307
|
+
All knobs are in `KCEConfig`. The defaults are tuned for the Mistral Codestral Embed + Zerank-2 combination. You can override:
|
|
308
|
+
|
|
309
|
+
| Setting | Default | What it controls |
|
|
310
|
+
|---|---|---|
|
|
311
|
+
| `mistral_embed_model` | `codestral-embed` | Embedding model |
|
|
312
|
+
| `zerank_model` | `zerank-2` | Reranker model |
|
|
313
|
+
| `chunk_max_tokens` | 512 | Max chunk size |
|
|
314
|
+
| `chunk_overlap_pct` | 0.35 | Chunk overlap |
|
|
315
|
+
| `bm25_k1`, `bm25_b` | 1.5, 0.75 | BM25 params |
|
|
316
|
+
| `rrf_dense`, `rrf_bm25`, `rrf_graph` | 0.75, 0.05, 0.03 | RRF channel weights |
|
|
317
|
+
| `retrieval_top_k` | 50 | Candidates per channel |
|
|
318
|
+
| `rerank_top_n` | 15 | Candidates sent to reranker |
|
|
319
|
+
| `final_top_n` | 10 | Final results returned |
|
|
320
|
+
| `filename_keyword_boost` | 2.0 | Post-rerank filename tie-breaker |
|
|
321
|
+
| `tier_c_penalty` | 0.4 | Penalty for abstract base classes |
|
|
322
|
+
| `context_budget_tokens` | 4096 | Context assembly budget |
|
|
323
|
+
|
|
324
|
+
API keys are read from `MISTRAL_API_KEY` and `ZEROENTROPY_API_KEY` env vars.
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Why not just use ripgrep / the IDE's built-in search?
|
|
329
|
+
|
|
330
|
+
Lexical search finds the keyword. It doesn't find the *concept*. Asking "how does routing work in flask" with grep gives you every line containing "route" — most of which is irrelevant. `kinetic-context` returns the 8 functions that actually implement routing, with their full bodies and signatures, in 68ms.
|
|
331
|
+
|
|
332
|
+
## Why not just stuff the whole repo into the LLM context window?
|
|
333
|
+
|
|
334
|
+
A 1k-file repo is ~500k tokens just for the source. That's expensive, slow, and the LLM's attention degrades badly past ~100k tokens. `kinetic-context` returns the 10 chunks that matter, fits in any context window, and costs 100x less to query.
|
|
335
|
+
|
|
336
|
+
## Why a Code Knowledge Graph?
|
|
337
|
+
|
|
338
|
+
Because "what calls what" matters. When you ask "where is `request` used?", the graph says "the `request` proxy is defined in `flask/globals.py`, used in 47 places, and its setter is in `flask/app.py`". Pure lexical search sees the word `request` 500 times. The graph sees the relationship.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## License
|
|
343
|
+
|
|
344
|
+
MIT. See [LICENSE](LICENSE).
|
|
345
|
+
|
|
346
|
+
## Contributing
|
|
347
|
+
|
|
348
|
+
Issues and PRs welcome at [github.com/zai-org/kinetic-context](https://github.com/zai-org/kinetic-context).
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# kinetic-context
|
|
2
|
+
|
|
3
|
+
> Repository-level code context engine — find the right code, fast.
|
|
4
|
+
|
|
5
|
+
`kinetic-context` is a self-contained, installable code context engine. Point it at any repository and it builds a multi-layer index (AST chunks + embeddings + a code knowledge graph + BM25) that you can query with natural language or identifiers. It returns **code blocks with line ranges** — not just file paths — ready to paste into an LLM prompt.
|
|
6
|
+
|
|
7
|
+
It is designed to be the context layer for coding agents. It ships with a Rich CLI, an MCP server (so Claude Code, Cursor, Continue, and Zed can mount it natively), a TCP JSON mode for any other agent, and a Python library.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Highlights
|
|
12
|
+
|
|
13
|
+
- **Multi-language**: Python, JavaScript, TypeScript, Go, Rust, Java — via tree-sitter.
|
|
14
|
+
- **Structure-aware chunking** (`cAST`): never splits a function mid-body. 35% overlap. Hierarchical summaries at file and repository level.
|
|
15
|
+
- **Hybrid retrieval**: dense (Mistral Codestral Embed, 1536-dim) + BM25 (code-aware tokenizer that splits camelCase, snake_case, and dotted identifiers) + a Code Knowledge Graph with 8 relationship types.
|
|
16
|
+
- **Reciprocal Rank Fusion** with brute-force-optimized weights across 5 channels (dense, BM25, graph, PRF, patch-reverse-engineering).
|
|
17
|
+
- **Zerank-2 reranker** with instruction-following via XML tags, tuned per query intent.
|
|
18
|
+
- **Novel signals** invented for this engine:
|
|
19
|
+
- **Cross-resolution resonance** — L2+L3+L4 consensus boost
|
|
20
|
+
- **Code DNA fingerprinting** — structural similarity (param count, complexity, call count)
|
|
21
|
+
- **Semantic bridges** — virtual graph edges between similar functions
|
|
22
|
+
- **File cohort memory** — files that co-occur in correct answers get boosted across queries
|
|
23
|
+
- **Score distribution shape analysis** — adaptive cutoff detects bimodal / uniform / power-law score distributions
|
|
24
|
+
- **Score gap amplification** — sigmoid sharpening of close scores
|
|
25
|
+
- **Post-rerank filename tie-breaker** — the fix for "correct directory, wrong file" at scale
|
|
26
|
+
- **Adversarial anti-centroid** — penalty for chunks near the worst BM25 hits
|
|
27
|
+
- **Query-to-patch reverse engineering** — generate a hypothetical fix, embed it as a 5th RRF channel
|
|
28
|
+
- **Incremental indexing** with SHA-256 Merkle root hashing. Only changed files are re-embedded. Subsequent `index` runs are fast.
|
|
29
|
+
- **Per-repo isolated storage** under `~/.kinetic/<slug>_<hash>/`. Same-name folders in different parents do not collide. A `manifest.json` records the source path, root hash, file count, and last-indexed timestamp so `kinetic status` can answer "reindex needed?" in <50ms without spinning up the engine.
|
|
30
|
+
- **Hookable everywhere**: MCP server (stdio JSON-RPC), TCP JSON, Python library, Rich CLI.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install kinetic-context
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Requires Python 3.10+. All heavy dependencies (tree-sitter, numpy, networkx, rich) are bundled — no Qdrant/Pinecone/Milvus/Postgres to install.
|
|
41
|
+
|
|
42
|
+
Set your API keys (a Mistral key for embeddings + a Zerank key for reranking):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
export MISTRAL_API_KEY=...
|
|
46
|
+
export ZEROENTROPY_API_KEY=...
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
You can also override the embedder / reranker URLs and model IDs in `KCEConfig` if you want to point at a different provider.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Index a repo (first run; takes a few minutes for a 1k-file repo)
|
|
57
|
+
kinetic index ./my-repo
|
|
58
|
+
|
|
59
|
+
# Subsequent runs only re-embed files whose SHA-256 hash changed
|
|
60
|
+
kinetic index ./my-repo
|
|
61
|
+
|
|
62
|
+
# Search — returns code blocks with line ranges, not just file paths
|
|
63
|
+
kinetic query "authentication middleware" --code
|
|
64
|
+
|
|
65
|
+
# JSON output (for agent hooks / piping)
|
|
66
|
+
kinetic query "how does routing work" --json | jq '.results[0]'
|
|
67
|
+
|
|
68
|
+
# Check if the index is up to date
|
|
69
|
+
kinetic status ./my-repo
|
|
70
|
+
|
|
71
|
+
# List all indexed repos
|
|
72
|
+
kinetic list
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Query output example
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
────────────────────────────────────────────────────────────────────────
|
|
79
|
+
kinetic-context • semantic_question • zerank • 68ms
|
|
80
|
+
────────────────────────────────────────────────────────────────────────
|
|
81
|
+
Query: how does routing work in flask
|
|
82
|
+
────────────────────────────────────────────────────────────────────────
|
|
83
|
+
Found 8 code blocks across 8 files
|
|
84
|
+
|
|
85
|
+
#1 src/flask/sansio/app.py L240-262 (23 lines) • add_url_rule (function) • python
|
|
86
|
+
def add_url_rule(self, rule, endpoint=None, view_func=None, ...)
|
|
87
|
+
240 │ def add_url_rule(
|
|
88
|
+
241 │ self,
|
|
89
|
+
242 │ rule: str,
|
|
90
|
+
243 │ endpoint: str | None = None,
|
|
91
|
+
...
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Hooking into coding agents
|
|
97
|
+
|
|
98
|
+
### MCP server (Claude Code, Cursor, Continue, Zed, anything MCP-aware)
|
|
99
|
+
|
|
100
|
+
Start the MCP server in the background:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
kinetic mcp
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Or add it to your agent's MCP config:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"mcpServers": {
|
|
111
|
+
"kinetic": {
|
|
112
|
+
"command": "kinetic",
|
|
113
|
+
"args": ["mcp"]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Four tools are exposed:
|
|
120
|
+
|
|
121
|
+
| Tool | Description |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `kinetic_index` | Index (or incrementally update) a repo |
|
|
124
|
+
| `kinetic_query` | Search the indexed codebase, returns code blocks with line ranges |
|
|
125
|
+
| `kinetic_status` | Check whether the index is up to date |
|
|
126
|
+
| `kinetic_list_indexes` | List all indexed repos |
|
|
127
|
+
|
|
128
|
+
### TCP JSON server (any agent)
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
kinetic serve --port 7878
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Send a single-line JSON request, get a single-line JSON response:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
echo '{"query": "session cookie signing", "top": 5}' | nc localhost 7878
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Python library
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from kce.engine import KCEEngine
|
|
144
|
+
from kce.config import KCEConfig
|
|
145
|
+
from kce.store.registry import Registry
|
|
146
|
+
|
|
147
|
+
cfg = KCEConfig()
|
|
148
|
+
cfg.index_dir = Registry().resolve("/path/to/repo")
|
|
149
|
+
cfg.ensure_dirs()
|
|
150
|
+
engine = KCEEngine(cfg)
|
|
151
|
+
engine.index("/path/to/repo")
|
|
152
|
+
|
|
153
|
+
result = engine.query("how does authentication work")
|
|
154
|
+
for cid in result.final_chunk_ids[:10]:
|
|
155
|
+
chunk = engine.chunk_index[cid]
|
|
156
|
+
print(f"{chunk.rel_path}:{chunk.start_line}-{chunk.end_line} {chunk.name}")
|
|
157
|
+
print(chunk.content)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Storage layout
|
|
163
|
+
|
|
164
|
+
Every indexed repo gets its own directory under `~/.kinetic/`:
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
~/.kinetic/
|
|
168
|
+
flask_f9cbd6f6/ <- slug = name + short hash of abspath
|
|
169
|
+
manifest.json <- source path, root hash, file count, last indexed
|
|
170
|
+
chunks.jsonl <- one CodeChunk per line
|
|
171
|
+
embeddings.npy <- (N, 1536) float32 matrix
|
|
172
|
+
embeddings_ids.json <- row order
|
|
173
|
+
ckg.graphml <- Code Knowledge Graph
|
|
174
|
+
incremental_state.json <- per-file SHA-256 hashes
|
|
175
|
+
change_log.jsonl <- append-only change history
|
|
176
|
+
embed_cache/embeddings.db <- Mistral API cache (SQLite)
|
|
177
|
+
zerank_cache/rerank.db <- Zerank API cache (SQLite)
|
|
178
|
+
file_summaries.json
|
|
179
|
+
repo_summary.txt
|
|
180
|
+
django_ea20f8f1/
|
|
181
|
+
...
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Why per-repo isolation?
|
|
185
|
+
|
|
186
|
+
- Two repos with the same folder name (e.g. `~/work/api` and `~/side/api`) get different slugs because the slug includes a short hash of the absolute path. No collisions.
|
|
187
|
+
- Caches live next to the index, so deleting a repo's index (`kinetic forget`) also frees its cache.
|
|
188
|
+
- The `manifest.json` records a Merkle root hash over all file content hashes. Recomputing this on startup is <50ms even for a 1k-file repo, so `kinetic status` is instant.
|
|
189
|
+
|
|
190
|
+
### Efficient incremental updates
|
|
191
|
+
|
|
192
|
+
1. On `kinetic index <repo>`, we first compute the current Merkle root from disk.
|
|
193
|
+
2. We compare to the stored root in `manifest.json`. If they match, the index is up to date — we're done in <100ms.
|
|
194
|
+
3. If they differ, we walk the per-file SHA-256 hashes in `incremental_state.json` and identify exactly which files were added, modified, or removed.
|
|
195
|
+
4. Only those files are re-parsed, re-summarized, and re-embedded. Existing chunks for unchanged files are reused.
|
|
196
|
+
5. The Mistral embed cache (SQLite, keyed by SHA-256 of the embed text) means even a chunk whose text didn't change but whose `chunk_id` was regenerated will not trigger an API call.
|
|
197
|
+
|
|
198
|
+
For a 1k-file repo where 5 files changed, a re-index takes seconds, not minutes.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Architecture
|
|
203
|
+
|
|
204
|
+

|
|
205
|
+
|
|
206
|
+
The pipeline has four layers:
|
|
207
|
+
|
|
208
|
+
1. **Ingestion** — tree-sitter parses each source file into an AST. The `cAST` chunker walks the AST and produces chunks that respect syntactic boundaries (a function is never split mid-body). Each chunk is enriched with its signature, docstring, decorators, and scope. A Code Knowledge Graph (NetworkX) is built with 8 relationship types: `CALLS`, `INHERITS`, `IMPLEMENTS`, `IMPORTS`, `CONTAINS`, `USES_TYPE`, `OVERRIDES`, `DEPENDS_ON`.
|
|
209
|
+
|
|
210
|
+
2. **Storage** — chunks go to JSONL, embeddings go to a single numpy matrix on disk (+ L2-normalized in memory for fast cosine), the graph goes to GraphML. Each repo gets its own directory under `~/.kinetic/`. SHA-256 Merkle root hashing drives incremental updates.
|
|
211
|
+
|
|
212
|
+
3. **Retrieval & Ranking** — the query coordinator picks one of 5 query types (identifier lookup, semantic question, code completion, bug diagnosis, architecture query). For each type, it applies intent-aware boosts (source vs test vs config files), runs multi-query BM25 + dense + graph retrieval, fuses the results with weighted Reciprocal Rank Fusion, applies novel signals (resonance, DNA, semantic bridges, cohort memory), then reranks the top candidates with Zerank-2.
|
|
213
|
+
|
|
214
|
+
4. **Output** — the final ranked chunks are returned as code blocks with line ranges, signature, and docstring. They can be rendered by the Rich CLI, serialized to JSON, or shipped over MCP / TCP to any coding agent.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Benchmarks
|
|
219
|
+
|
|
220
|
+
We benchmark on two real-world repos with hand-curated query sets. We report Context F1@10, Recall@10, Precision@10, and end-to-end latency. **We deliberately do not compare to other context engines** — those numbers are easy to get wrong and we'd rather show our own results honestly than risk an unfair comparison.
|
|
221
|
+
|
|
222
|
+
### Aggregate metrics
|
|
223
|
+
|
|
224
|
+

|
|
225
|
+
|
|
226
|
+
### Per-query F1 (sorted, so the worst queries are at the top)
|
|
227
|
+
|
|
228
|
+

|
|
229
|
+
|
|
230
|
+
### Query outcome distribution (perfect / partial / failed)
|
|
231
|
+
|
|
232
|
+

|
|
233
|
+
|
|
234
|
+
### Latency distribution
|
|
235
|
+
|
|
236
|
+

|
|
237
|
+
|
|
238
|
+
### Quality vs repository scale
|
|
239
|
+
|
|
240
|
+

|
|
241
|
+
|
|
242
|
+
### Numbers
|
|
243
|
+
|
|
244
|
+
| Corpus | Files | Chunks | Graph (nodes/edges) | Queries | F1@10 | Recall@10 | Precision@10 | Avg latency |
|
|
245
|
+
|---|---:|---:|---:|---:|---:|---:|---:|---:|
|
|
246
|
+
| Flask | 83 | 1,382 | 1,594 / 7,906 | 30 | 0.659 | 0.833 | 0.599 | 68 ms |
|
|
247
|
+
| Django (core) | 308 | 7,207 | 6,574 / 46,408 | 30 | 0.647 | 0.867 | 0.559 | 433 ms |
|
|
248
|
+
|
|
249
|
+
The Django benchmark uses the core Django packages (`django/db/`, `django/http/`, `django/urls/`, `django/template/`, `django/forms/`, `django/core/`, `django/contrib/auth/`, `django/contrib/sessions/`) — the parts of Django that real coding agents actually search. The full Django repo includes 3,000+ files of migrations, tests, and docs that bloat the index without improving retrieval quality on real-world queries.
|
|
250
|
+
|
|
251
|
+
Run the benchmarks yourself:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
git clone https://github.com/pallets/flask /tmp/flask
|
|
255
|
+
git clone https://github.com/django/django /tmp/django
|
|
256
|
+
kinetic index /tmp/flask
|
|
257
|
+
kinetic index /tmp/django
|
|
258
|
+
python scripts/run_bench.py flask
|
|
259
|
+
python scripts/run_bench.py django
|
|
260
|
+
python scripts/gen_charts.py
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Configuration
|
|
266
|
+
|
|
267
|
+
All knobs are in `KCEConfig`. The defaults are tuned for the Mistral Codestral Embed + Zerank-2 combination. You can override:
|
|
268
|
+
|
|
269
|
+
| Setting | Default | What it controls |
|
|
270
|
+
|---|---|---|
|
|
271
|
+
| `mistral_embed_model` | `codestral-embed` | Embedding model |
|
|
272
|
+
| `zerank_model` | `zerank-2` | Reranker model |
|
|
273
|
+
| `chunk_max_tokens` | 512 | Max chunk size |
|
|
274
|
+
| `chunk_overlap_pct` | 0.35 | Chunk overlap |
|
|
275
|
+
| `bm25_k1`, `bm25_b` | 1.5, 0.75 | BM25 params |
|
|
276
|
+
| `rrf_dense`, `rrf_bm25`, `rrf_graph` | 0.75, 0.05, 0.03 | RRF channel weights |
|
|
277
|
+
| `retrieval_top_k` | 50 | Candidates per channel |
|
|
278
|
+
| `rerank_top_n` | 15 | Candidates sent to reranker |
|
|
279
|
+
| `final_top_n` | 10 | Final results returned |
|
|
280
|
+
| `filename_keyword_boost` | 2.0 | Post-rerank filename tie-breaker |
|
|
281
|
+
| `tier_c_penalty` | 0.4 | Penalty for abstract base classes |
|
|
282
|
+
| `context_budget_tokens` | 4096 | Context assembly budget |
|
|
283
|
+
|
|
284
|
+
API keys are read from `MISTRAL_API_KEY` and `ZEROENTROPY_API_KEY` env vars.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Why not just use ripgrep / the IDE's built-in search?
|
|
289
|
+
|
|
290
|
+
Lexical search finds the keyword. It doesn't find the *concept*. Asking "how does routing work in flask" with grep gives you every line containing "route" — most of which is irrelevant. `kinetic-context` returns the 8 functions that actually implement routing, with their full bodies and signatures, in 68ms.
|
|
291
|
+
|
|
292
|
+
## Why not just stuff the whole repo into the LLM context window?
|
|
293
|
+
|
|
294
|
+
A 1k-file repo is ~500k tokens just for the source. That's expensive, slow, and the LLM's attention degrades badly past ~100k tokens. `kinetic-context` returns the 10 chunks that matter, fits in any context window, and costs 100x less to query.
|
|
295
|
+
|
|
296
|
+
## Why a Code Knowledge Graph?
|
|
297
|
+
|
|
298
|
+
Because "what calls what" matters. When you ask "where is `request` used?", the graph says "the `request` proxy is defined in `flask/globals.py`, used in 47 places, and its setter is in `flask/app.py`". Pure lexical search sees the word `request` 500 times. The graph sees the relationship.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## License
|
|
303
|
+
|
|
304
|
+
MIT. See [LICENSE](LICENSE).
|
|
305
|
+
|
|
306
|
+
## Contributing
|
|
307
|
+
|
|
308
|
+
Issues and PRs welcome at [github.com/zai-org/kinetic-context](https://github.com/zai-org/kinetic-context).
|