witan-code 0.2.0__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.
- witan_code-0.2.0/.gitignore +38 -0
- witan_code-0.2.0/PKG-INFO +476 -0
- witan_code-0.2.0/README.md +449 -0
- witan_code-0.2.0/config/claude.json +16 -0
- witan_code-0.2.0/config/copilot.json +17 -0
- witan_code-0.2.0/config/pi.json +16 -0
- witan_code-0.2.0/docs/BRANCH_INDEXING.md +123 -0
- witan_code-0.2.0/docs/CLI_REFERENCE.md +153 -0
- witan_code-0.2.0/docs/EDGE_PRECISION_TIERS.md +104 -0
- witan_code-0.2.0/docs/PACKAGE_MAP.md +105 -0
- witan_code-0.2.0/docs/STAGE2_STITCHING.md +80 -0
- witan_code-0.2.0/docs/SYMBOL_FORMAT.md +142 -0
- witan_code-0.2.0/docs/SYMBOL_TABLE.md +105 -0
- witan_code-0.2.0/docs/USER_GUIDE.md +230 -0
- witan_code-0.2.0/install.sh +49 -0
- witan_code-0.2.0/pyproject.toml +65 -0
- witan_code-0.2.0/queries/code_mutations.gq +81 -0
- witan_code-0.2.0/queries/code_read.gq +152 -0
- witan_code-0.2.0/tests/__init__.py +0 -0
- witan_code-0.2.0/tests/conftest.py +53 -0
- witan_code-0.2.0/tests/test_branches.py +330 -0
- witan_code-0.2.0/tests/test_bridge.py +619 -0
- witan_code-0.2.0/tests/test_cli_hooks.py +47 -0
- witan_code-0.2.0/tests/test_context.py +149 -0
- witan_code-0.2.0/tests/test_crossrepo.py +77 -0
- witan_code-0.2.0/tests/test_detach.py +39 -0
- witan_code-0.2.0/tests/test_edges.py +397 -0
- witan_code-0.2.0/tests/test_elicit.py +458 -0
- witan_code-0.2.0/tests/test_enrichment.py +79 -0
- witan_code-0.2.0/tests/test_graph.py +39 -0
- witan_code-0.2.0/tests/test_hooks.py +188 -0
- witan_code-0.2.0/tests/test_indexer.py +48 -0
- witan_code-0.2.0/tests/test_languages.py +124 -0
- witan_code-0.2.0/tests/test_maintenance.py +243 -0
- witan_code-0.2.0/tests/test_package_map.py +136 -0
- witan_code-0.2.0/tests/test_repo.py +60 -0
- witan_code-0.2.0/tests/test_server.py +73 -0
- witan_code-0.2.0/tests/test_setup.py +266 -0
- witan_code-0.2.0/tests/test_stitch.py +404 -0
- witan_code-0.2.0/tests/test_symbol_table.py +342 -0
- witan_code-0.2.0/tests/test_visualize.py +358 -0
- witan_code-0.2.0/uv.lock +1694 -0
- witan_code-0.2.0/witan_code/__init__.py +0 -0
- witan_code-0.2.0/witan_code/__main__.py +4 -0
- witan_code-0.2.0/witan_code/_detach.py +27 -0
- witan_code-0.2.0/witan_code/bridge.py +392 -0
- witan_code-0.2.0/witan_code/bridge_extractors.py +642 -0
- witan_code-0.2.0/witan_code/cli.py +722 -0
- witan_code-0.2.0/witan_code/config.py +65 -0
- witan_code-0.2.0/witan_code/context.py +98 -0
- witan_code-0.2.0/witan_code/edges.py +171 -0
- witan_code-0.2.0/witan_code/elicit.py +92 -0
- witan_code-0.2.0/witan_code/extensions/pi/codegraph.ts +121 -0
- witan_code-0.2.0/witan_code/graph.py +271 -0
- witan_code-0.2.0/witan_code/hooks.py +116 -0
- witan_code-0.2.0/witan_code/indexer.py +1042 -0
- witan_code-0.2.0/witan_code/maintenance.py +130 -0
- witan_code-0.2.0/witan_code/package_map.py +74 -0
- witan_code-0.2.0/witan_code/queries/bridge.gq +193 -0
- witan_code-0.2.0/witan_code/queries/code_mutations.gq +81 -0
- witan_code-0.2.0/witan_code/queries/code_read.gq +154 -0
- witan_code-0.2.0/witan_code/queries/delete.gq +16 -0
- witan_code-0.2.0/witan_code/queries_ts/bash.scm +9 -0
- witan_code-0.2.0/witan_code/queries_ts/hcl.scm +16 -0
- witan_code-0.2.0/witan_code/queries_ts/python.scm +29 -0
- witan_code-0.2.0/witan_code/queries_ts/sql.scm +20 -0
- witan_code-0.2.0/witan_code/queries_ts/typescript.scm +51 -0
- witan_code-0.2.0/witan_code/queries_ts/yaml.scm +5 -0
- witan_code-0.2.0/witan_code/repo.py +282 -0
- witan_code-0.2.0/witan_code/schema/bridge-schema.pg +82 -0
- witan_code-0.2.0/witan_code/schema/code-schema.pg +51 -0
- witan_code-0.2.0/witan_code/server.py +859 -0
- witan_code-0.2.0/witan_code/setup.py +230 -0
- witan_code-0.2.0/witan_code/skills/witan-code/SKILL.md +97 -0
- witan_code-0.2.0/witan_code/stitch.py +177 -0
- witan_code-0.2.0/witan_code/store.py +116 -0
- witan_code-0.2.0/witan_code/visualize.py +347 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
.venv/
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.pyc
|
|
6
|
+
|
|
7
|
+
# Environment / secrets
|
|
8
|
+
.env
|
|
9
|
+
.env.local
|
|
10
|
+
.env.*.local
|
|
11
|
+
*.secret
|
|
12
|
+
|
|
13
|
+
# Editor
|
|
14
|
+
.vscode/settings.json
|
|
15
|
+
.idea/
|
|
16
|
+
*.swp
|
|
17
|
+
*.swo
|
|
18
|
+
|
|
19
|
+
# OS
|
|
20
|
+
.DS_Store
|
|
21
|
+
Thumbs.db
|
|
22
|
+
|
|
23
|
+
# Build artifacts
|
|
24
|
+
dist/
|
|
25
|
+
build/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
# omnigraph binary downloaded by hatch_build.py — not committed
|
|
28
|
+
_bin/
|
|
29
|
+
# omnigraph cluster state (populated by `omnigraph cluster import`)
|
|
30
|
+
__cluster/
|
|
31
|
+
|
|
32
|
+
# MCP server logs / caches
|
|
33
|
+
.mcp-cache/
|
|
34
|
+
mcp-server.log
|
|
35
|
+
.ruff_cache/
|
|
36
|
+
|
|
37
|
+
# agent-config-kit remote skill/hook fetch cache
|
|
38
|
+
.agent-config-kit-cache/
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: witan-code
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: witan-code — tree-sitter code graph + cross-repo bridge (mounts under `witan code`)
|
|
5
|
+
Project-URL: Homepage, https://github.com/mitodl/agent-kit/tree/main/mcp/servers/witan-code
|
|
6
|
+
Project-URL: Repository, https://github.com/mitodl/agent-kit
|
|
7
|
+
Project-URL: Documentation, https://github.com/mitodl/agent-kit/blob/main/mcp/servers/witan-code/README.md
|
|
8
|
+
Project-URL: Issues, https://github.com/mitodl/agent-kit/issues
|
|
9
|
+
License-Expression: BSD-3-Clause
|
|
10
|
+
Keywords: agent,code-graph,coding-agent,mcp,static-analysis,tree-sitter
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Requires-Dist: agent-config-kit<1,>=0.1
|
|
14
|
+
Requires-Dist: cyclopts<5,>=4
|
|
15
|
+
Requires-Dist: fastmcp<4,>=3.4.2
|
|
16
|
+
Requires-Dist: pyyaml<7,>=6
|
|
17
|
+
Requires-Dist: rich>=13
|
|
18
|
+
Requires-Dist: tree-sitter-bash<0.26,>=0.25
|
|
19
|
+
Requires-Dist: tree-sitter-go<0.26,>=0.25
|
|
20
|
+
Requires-Dist: tree-sitter-hcl<1.3,>=1.2
|
|
21
|
+
Requires-Dist: tree-sitter-python<0.26,>=0.25
|
|
22
|
+
Requires-Dist: tree-sitter-sql<0.4,>=0.3
|
|
23
|
+
Requires-Dist: tree-sitter-typescript<0.24,>=0.23
|
|
24
|
+
Requires-Dist: tree-sitter-yaml<0.8,>=0.7
|
|
25
|
+
Requires-Dist: tree-sitter<0.27,>=0.26
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# witan-code
|
|
29
|
+
|
|
30
|
+
> "witan" is pronounced `WIT-ən` (/ˈwɪtən/) — rhymes with "written" minus the r.
|
|
31
|
+
|
|
32
|
+
A tree-sitter-based **code graph** MCP server (Layer 2) for coding agents,
|
|
33
|
+
backed by [Omnigraph](https://github.com/ModernRelay/omnigraph). It indexes a
|
|
34
|
+
repository's symbols (functions, methods, classes, modules) and their
|
|
35
|
+
relationships, then exposes definition / reference / caller / impact queries to
|
|
36
|
+
the agent.
|
|
37
|
+
|
|
38
|
+
It is a self-contained sibling of `witan` (Layer 1) and shares its
|
|
39
|
+
subprocess/CLI conventions, but stores a **separate, per-repo, local-only**
|
|
40
|
+
graph.
|
|
41
|
+
|
|
42
|
+
> **Wiring it into your agents locally** (MCP server, the `PostToolUse` reindex
|
|
43
|
+
> hook, and the indexer CLI, run straight from your checkout): see
|
|
44
|
+
> [Local Development Setup](../../../docs/agent-memory.md#local-development-setup).
|
|
45
|
+
|
|
46
|
+
## When to use this vs. grep / the `Explore` agent
|
|
47
|
+
|
|
48
|
+
Reach for `code_*` tools when the question is **structural**, not textual:
|
|
49
|
+
exact symbol definitions, who calls/references a symbol (and transitively —
|
|
50
|
+
change-impact / blast radius before an edit), what a file defines, or which
|
|
51
|
+
other repo provides/consumes a shared contract (env var, endpoint, package,
|
|
52
|
+
service). Grep still wins for literal string/comment searches and one-off text
|
|
53
|
+
matches — it finds *text*; this finds **symbols and their relationships**,
|
|
54
|
+
including things grep structurally cannot answer (a function's transitive
|
|
55
|
+
callers, or which service consumes an endpoint another service serves). See
|
|
56
|
+
the `/witan-code` skill for a full tool-selection guide and reference table.
|
|
57
|
+
|
|
58
|
+
`Calls`/`References`/`Imports`/`Inherits` edges are heuristic (syntactic name
|
|
59
|
+
resolution, not a true call graph) — see
|
|
60
|
+
[Heuristic edges](#heuristic-edges-important) below.
|
|
61
|
+
|
|
62
|
+
## Two-layer composition
|
|
63
|
+
|
|
64
|
+
| Layer | Server | Stores | Scope | Synced |
|
|
65
|
+
|------|--------|--------|-------|--------|
|
|
66
|
+
| 1 | `witan` | patterns, project facts, lessons, workflow traces | team-wide | yes (S3) |
|
|
67
|
+
| 2 | `witan-code` | code symbols + edges | per-repo | **no — local only** |
|
|
68
|
+
|
|
69
|
+
The two layers compose through **soft symbol-ID references**. A Layer-1 node
|
|
70
|
+
(e.g. a `lesson` or `agent_context`) can record symbol ids of the form:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
repo#relative/path.py::QualifiedName
|
|
74
|
+
e.g. https://github.com/mitodl/ol-django#app/svc.py::Service.run
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
in a `symbolRefs`-style field. There is **no hard cross-store edge**: the id is
|
|
78
|
+
just a string that resolves in the code graph via `code_find_definition` /
|
|
79
|
+
`get_symbol`. This keeps the team-synced memory graph independent of any
|
|
80
|
+
machine's local code index.
|
|
81
|
+
|
|
82
|
+
## Documentation
|
|
83
|
+
|
|
84
|
+
- [User guide](https://github.com/mitodl/agent-kit/blob/main/mcp/servers/witan-code/docs/USER_GUIDE.md) — task-oriented walkthrough: install,
|
|
85
|
+
first index, definition/caller/impact queries, cross-repo bridge basics,
|
|
86
|
+
troubleshooting.
|
|
87
|
+
- [CLI reference](https://github.com/mitodl/agent-kit/blob/main/mcp/servers/witan-code/docs/CLI_REFERENCE.md) — every `witan-code` command with
|
|
88
|
+
its full flag table and an example invocation.
|
|
89
|
+
|
|
90
|
+
## Cross-repo context bridge (Layer 2.5)
|
|
91
|
+
|
|
92
|
+
The per-repo graph stops at a repo boundary, but service-oriented architectures
|
|
93
|
+
couple repos through **shared contracts**: an env var that infra sets and an app
|
|
94
|
+
reads, an HTTP endpoint one service serves and another calls, a package one repo
|
|
95
|
+
publishes and others import. The bridge records these as **interface bindings**
|
|
96
|
+
in a single shared, local-only store (`_bridge.omni`, a sibling of the per-repo
|
|
97
|
+
stores) so linkages can be queried across every indexed repo.
|
|
98
|
+
|
|
99
|
+
It is **zero-config**: every `index`/reindex of a repo also extracts that repo's
|
|
100
|
+
bindings into the bridge store. Linkages are then computed by grouping bindings
|
|
101
|
+
on `(kind, key_norm)` — no registry of repos to maintain, no link edges.
|
|
102
|
+
|
|
103
|
+
Four binding kinds (all extraction is **heuristic/syntactic**, like the symbol
|
|
104
|
+
edges):
|
|
105
|
+
|
|
106
|
+
| Kind | Provider | Consumer | Join key |
|
|
107
|
+
|---|---|---|---|
|
|
108
|
+
| `env_var` | Pulumi `<app>:env_vars` keys in `Pulumi.*.yaml` | `get_string("NAME",…)` / `os.environ` / `process.env.X` / `env("NAME")` | the `UPPER_SNAKE` name |
|
|
109
|
+
| `endpoint` | drf-spectacular OpenAPI spec (`paths.<path>.<method>`) | `/api/…` path literals in TS (generated client / fetch) | normalized path (params → `{}`) |
|
|
110
|
+
| `package` | a repo whose `package.json` name is `@mitodl/*` | `from mitol.*` / `include("mitol.*")` / `@mitodl/*` imports | the package name |
|
|
111
|
+
| `service` | `applications/<svc>/__main__.py` (repo / image / service name) | the deployed repo itself (key_norm = its canonical URI) | `repo:<uri>` / `image:…` / `name:…` |
|
|
112
|
+
|
|
113
|
+
Generic env names (`DEBUG`, `PORT`, `SECRET_KEY`, …) are flagged `generic` and
|
|
114
|
+
excluded from cross-repo impact fan-out so a trivial edit doesn't appear to touch
|
|
115
|
+
every repo.
|
|
116
|
+
|
|
117
|
+
The bridge is written in a **separate phase after the per-repo store write**, so
|
|
118
|
+
the two stores' advisory write locks never nest (no deadlock) and a bridge
|
|
119
|
+
failure never corrupts a per-repo store. A full-repo index runs the repo-level
|
|
120
|
+
provider extractors (OpenAPI / Pulumi / service / `package.json`) and clears
|
|
121
|
+
bindings for files deleted from disk; all purging is per-file, so unchanged
|
|
122
|
+
(skipped) files — and, for a narrow target like the reindex hook, sibling
|
|
123
|
+
files — keep their bindings.
|
|
124
|
+
|
|
125
|
+
Every binding also carries a **canonical symbol string**
|
|
126
|
+
(`{scheme}:{manager}:{package}:{version}:{descriptor}`, SCIP-inspired) — the
|
|
127
|
+
join key for the precise cross-repo linking tier. Provider symbols are
|
|
128
|
+
qualified by the repo's declared package identity from an optional
|
|
129
|
+
`witan-code.toml` at the repo root; repos without one get a fallback identity
|
|
130
|
+
derived from the repo URI. See [docs/SYMBOL_FORMAT.md](docs/SYMBOL_FORMAT.md)
|
|
131
|
+
and [docs/PACKAGE_MAP.md](docs/PACKAGE_MAP.md).
|
|
132
|
+
|
|
133
|
+
Each bridge write also rebuilds the repo's **symbol table** — one `RepoSymbol`
|
|
134
|
+
row per (repo, role, symbol): `exported` rows are the repo's public contract
|
|
135
|
+
surface, `external` rows are unresolved references to other repos' contracts.
|
|
136
|
+
This deduplicated table is the Stage-2 read-time join artifact; inspect it
|
|
137
|
+
with `witan code symbols`. See [docs/SYMBOL_TABLE.md](docs/SYMBOL_TABLE.md).
|
|
138
|
+
|
|
139
|
+
Stage 2 itself — joining external symbol references against other repos'
|
|
140
|
+
exported symbols by canonical string, entirely at read time, never stored —
|
|
141
|
+
is `witan_code.stitch.resolve()`. Inspect it with `witan code stitch`
|
|
142
|
+
(`--unresolved` for indexing-coverage gaps) or the `code_precise_edges` /
|
|
143
|
+
`code_unresolved_symbols` MCP tools. See
|
|
144
|
+
[docs/STAGE2_STITCHING.md](docs/STAGE2_STITCHING.md).
|
|
145
|
+
|
|
146
|
+
Precise (Stage 2) and heuristic (Stage 3) edges are merged into one **typed,
|
|
147
|
+
precision-tiered** result by `witan_code.edges.cross_repo_edges()`. Every
|
|
148
|
+
tool that produces cross-repo links — `witan code deps`,
|
|
149
|
+
`code_interface_providers`/`_consumers`/`_search`, `code_cross_repo_impact` —
|
|
150
|
+
accepts `min_precision` (`"precise" | "heuristic" | "fuzzy"`, default
|
|
151
|
+
`"heuristic"` = current behavior unchanged). See
|
|
152
|
+
[docs/EDGE_PRECISION_TIERS.md](docs/EDGE_PRECISION_TIERS.md).
|
|
153
|
+
|
|
154
|
+
## Heuristic edges (important)
|
|
155
|
+
|
|
156
|
+
`Defines` and `Contains` are exact (derived from the syntax tree). But:
|
|
157
|
+
|
|
158
|
+
- **`Calls`**, **`References`**, **`Imports`**, and **`Inherits`** are
|
|
159
|
+
**HEURISTIC**. They are produced by syntactic name resolution: a call/base/
|
|
160
|
+
import identifier is matched to a known `Symbol` name, preferring same-file
|
|
161
|
+
definitions. Dynamic dispatch, re-exports, shadowing, and cross-file
|
|
162
|
+
resolution beyond name matching are **not** modeled precisely.
|
|
163
|
+
|
|
164
|
+
Treat caller/impact results as a high-recall starting point for investigation,
|
|
165
|
+
not a verified call graph.
|
|
166
|
+
|
|
167
|
+
## Symbol id scheme
|
|
168
|
+
|
|
169
|
+
- `CodeFile.id` = `repo#relative/path.py`
|
|
170
|
+
- `Symbol.id` = `repo#relative/path.py::QualifiedName` (e.g. `Class.method`)
|
|
171
|
+
|
|
172
|
+
The repo key is the canonical HTTPS remote URI (`https://github.com/org/repo`,
|
|
173
|
+
matching `witan`'s `repo.py`), falling back to the git-root directory
|
|
174
|
+
name when there is no remote.
|
|
175
|
+
|
|
176
|
+
## Supported languages
|
|
177
|
+
|
|
178
|
+
| Language | Extensions | Symbols extracted |
|
|
179
|
+
|---|---|---|
|
|
180
|
+
| Python | `.py` `.pyi` | functions, classes, methods, module |
|
|
181
|
+
| TypeScript / JS / JSX / TSX | `.ts` `.tsx` `.mts` `.cts` `.js` `.jsx` `.mjs` `.cjs` | functions, arrow consts, classes, methods (incl. arrow fields), interfaces, types, enums |
|
|
182
|
+
| Bash / Zsh | `.sh` `.bash` `.zsh` | functions |
|
|
183
|
+
| YAML | `.yaml` `.yml` | mapping keys as dotted paths (e.g. `jobs.build.steps`) |
|
|
184
|
+
|
|
185
|
+
Each symbol also carries **richer attributes** for agent context: the full
|
|
186
|
+
`signature` (name + multi-line params + return/param types), a `docstring`
|
|
187
|
+
(Python docstrings and TS/JS `/** … */` JSDoc), and `decorators`
|
|
188
|
+
(`@app.route(...)`, `@task`, `@Input()`, … — framework semantics that also feed
|
|
189
|
+
the cross-repo bridge). These are returned by `code_find_definition` /
|
|
190
|
+
`code_search_symbol` / `code_symbols_in_file`.
|
|
191
|
+
|
|
192
|
+
All JS/TS variants are parsed with the `tsx` grammar (a superset of TS, JS, and
|
|
193
|
+
JSX) — the plain `javascript` grammar rejects the TS node types in the shared
|
|
194
|
+
query. YAML indexes every mapping key as a navigable `key` symbol, so large
|
|
195
|
+
config trees (CI, k8s, Pulumi) can be searched by path; expect many such symbols.
|
|
196
|
+
|
|
197
|
+
Grammars come from **individual `tree-sitter-<lang>` wheels** (e.g.
|
|
198
|
+
`tree-sitter-python`), loaded as standalone `tree_sitter.Language`s — not
|
|
199
|
+
`tree-sitter-language-pack` (whose 1.9 line returns an incompatible binding and
|
|
200
|
+
downloads grammars on demand). Adding a language means: add its
|
|
201
|
+
`tree-sitter-<lang>` wheel to `pyproject.toml` (tight-pinned), add an entry to
|
|
202
|
+
`indexer._GRAMMAR_MODULES` (grammar name → module + capsule factory), add one
|
|
203
|
+
`LanguageSpec` (extensions + grammar name + a `queries_ts/<lang>.scm` capture
|
|
204
|
+
file + a capture→kind map), and any new node types to `_DEF_NODE_TYPES`.
|
|
205
|
+
|
|
206
|
+
### Not indexed yet (from a file-type survey of ol-infrastructure + mit-learn)
|
|
207
|
+
|
|
208
|
+
- **HCL / Terraform / Packer** (`.hcl`, `.tf`) — the strongest candidate to add
|
|
209
|
+
next (~50 files in ol-infrastructure); grammar is available and blocks map to
|
|
210
|
+
symbols (`variable.x`, `source.amazon-ebs.caddy`, `resource.<type>.<name>`).
|
|
211
|
+
- **VCL** (Varnish/Fastly, `.vcl`) — no published `tree-sitter-vcl` wheel, so it
|
|
212
|
+
can't be added without vendoring/building one.
|
|
213
|
+
- **Markdown** (`.md`/`.mdx`) — could index headings as a doc outline, but adds
|
|
214
|
+
low-signal symbols; left out for now.
|
|
215
|
+
- Config/markup (`.json`, `.toml`, `.ini`, `.scss`, `.html`, `.j2`) is not
|
|
216
|
+
symbol-bearing enough to index. Go/Rust grammars exist but neither repo uses them.
|
|
217
|
+
|
|
218
|
+
## Install
|
|
219
|
+
|
|
220
|
+
witan-code is usually installed as part of the `witan` umbrella — `witan setup`
|
|
221
|
+
wires both servers together via `uvx --from … --with …`. See the
|
|
222
|
+
[witan README](../witan/README.md) for the one-step setup.
|
|
223
|
+
|
|
224
|
+
To use witan-code **standalone** (code graph only, no memory/task tools):
|
|
225
|
+
|
|
226
|
+
**From PyPI:**
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# One-shot (uvx):
|
|
230
|
+
uvx --from witan-code witan-code index
|
|
231
|
+
|
|
232
|
+
# Persistent CLI install:
|
|
233
|
+
uv tool install witan-code
|
|
234
|
+
witan-code index
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**From the published git repo** (to track pre-release/unreleased code):
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# One-shot (uvx):
|
|
241
|
+
uvx --from git+https://github.com/mitodl/agent-kit#subdirectory=mcp/servers/witan-code \
|
|
242
|
+
witan-code index
|
|
243
|
+
|
|
244
|
+
# Persistent CLI install:
|
|
245
|
+
uv tool install git+https://github.com/mitodl/agent-kit#subdirectory=mcp/servers/witan-code
|
|
246
|
+
witan-code index
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**From a local checkout (inside `mcp/servers/witan-code/`):**
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
uvx --from . witan-code index # incremental
|
|
253
|
+
uvx --from . witan-code reindex # force rebuild
|
|
254
|
+
uvx --from . witan-code index path/to/file.py # single file/subpath
|
|
255
|
+
|
|
256
|
+
# Or via uv run:
|
|
257
|
+
uv run witan-code index
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Full standalone install:** `witan-code setup` installs everything a
|
|
261
|
+
witan-code-only deployment needs — the `omnigraph` binary, the `/witan-code`
|
|
262
|
+
skill, all four hooks (or the Pi extension), and the MCP server entry — for
|
|
263
|
+
one agent or all detected ones:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
witan-code setup # Claude Code (default)
|
|
267
|
+
witan-code setup --agent pi # Pi
|
|
268
|
+
witan-code setup --agent all # every detected agent
|
|
269
|
+
witan-code setup --dry-run # preview without writing
|
|
270
|
+
witan-code setup --author "Jane Doe" # attribution (default: git config user.name)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
If `witan` is *also* installed and witan-code is importable in that same
|
|
274
|
+
environment (e.g. via the `--with` in the `uv tool install`/MCP server's
|
|
275
|
+
`uvx` invocation), `witan setup` folds this same bundle in automatically —
|
|
276
|
+
skill and hooks (registered as `witan code …`, so only `witan` needs to be on
|
|
277
|
+
`PATH`), but **not** a second MCP server entry, since `witan serve` already
|
|
278
|
+
mounts witan-code's tools in-process. One `witan setup` then covers both
|
|
279
|
+
packages, and a separate `witan-code setup` isn't required (though re-running
|
|
280
|
+
it afterwards is harmless — `apply()` is an idempotent read-merge-write, and
|
|
281
|
+
it *will* add its own standalone MCP entry/`witan-code …` hooks alongside
|
|
282
|
+
witan's). Run `witan-code setup` on its own for a witan-code-only install, or
|
|
283
|
+
when witan-code isn't importable from witan's environment. See
|
|
284
|
+
[Hooks](#hooks) and [Skill](#skill).
|
|
285
|
+
|
|
286
|
+
This downloads the omnigraph release pinned by `_OMNIGRAPH_VERSION` in
|
|
287
|
+
[`witan_code/setup.py`](./witan_code/setup.py) — the same pin `witan`'s own
|
|
288
|
+
`setup.py` uses, kept in lockstep by Renovate (see the repo-root
|
|
289
|
+
`renovate.json`'s `omnigraph-version` customManager, which bumps both files
|
|
290
|
+
in one PR). There is no build-time bundling of the binary into the wheel —
|
|
291
|
+
`witan-code setup` (or `witan setup`) is the only source of the binary, and
|
|
292
|
+
re-running it is how you pick up a version bump.
|
|
293
|
+
|
|
294
|
+
Per-repo stores are created **lazily** on the first index — the indexer runs
|
|
295
|
+
`omnigraph init --schema code-schema.pg <store>` when the store is missing.
|
|
296
|
+
The `./install.sh` script only verifies the omnigraph binary (installing the
|
|
297
|
+
latest upstream release directly if missing, independent of the
|
|
298
|
+
`witan-code setup`/`_OMNIGRAPH_VERSION` pin) and prints a hint; it is not
|
|
299
|
+
required when installing via uvx/uv.
|
|
300
|
+
|
|
301
|
+
**Manual / unsupported-agent install:** for an agent `witan-code setup`
|
|
302
|
+
doesn't cover, copy the appropriate MCP snippet from `config/` into your
|
|
303
|
+
agent's config, and see [Hooks](#hooks)/[Skill](#skill) for the manual
|
|
304
|
+
symlink alternative:
|
|
305
|
+
|
|
306
|
+
- pi: `config/pi.json` → `~/.pi/agent/mcp.json`
|
|
307
|
+
- Claude: `config/claude.json` → `claude_desktop_config.json`
|
|
308
|
+
- Copilot: `config/copilot.json` → `.vscode/mcp.json`
|
|
309
|
+
|
|
310
|
+
## Environment variables
|
|
311
|
+
|
|
312
|
+
| Variable | Default | Purpose |
|
|
313
|
+
|----------|---------|---------|
|
|
314
|
+
| `WITAN_CODE_DIR` | `~/.local/share/witan/code` | directory of per-repo `<slug>.omni` stores |
|
|
315
|
+
| `WITAN_AUTHOR` / `USER` | `unknown` | attribution string |
|
|
316
|
+
| `WITAN_REPO` | — | override the detected repo slug |
|
|
317
|
+
| `WITAN_CODE_OPTIMIZE_INTERVAL` | `86400` (daily) | throttle window (seconds) for `checkpoint`'s opportunistic store compaction; `0` disables it |
|
|
318
|
+
|
|
319
|
+
The store URI for a repo is `<dir>/<sanitized-slug>.omni`, where the slug's `/`
|
|
320
|
+
and `:` are replaced with `_`. The shared cross-repo bridge lives alongside them
|
|
321
|
+
at `<dir>/_bridge.omni` and is created lazily on the first index that yields any
|
|
322
|
+
bindings.
|
|
323
|
+
|
|
324
|
+
## MCP tools
|
|
325
|
+
|
|
326
|
+
Each tool resolves the per-repo store from the current working directory and
|
|
327
|
+
returns `[]` / `null` gracefully when the store does not exist yet (run the
|
|
328
|
+
indexer first).
|
|
329
|
+
|
|
330
|
+
| Tool | Returns |
|
|
331
|
+
|------|---------|
|
|
332
|
+
| `code_find_definition(name)` | symbols whose name / qualified name matches |
|
|
333
|
+
| `code_find_references(symbol_id)` | incoming `References` + `Calls` |
|
|
334
|
+
| `code_callers(symbol_id)` | incoming `Calls` |
|
|
335
|
+
| `code_impact(symbol_id, max_depth=5, max_nodes=200)` | transitive callers via BFS |
|
|
336
|
+
| `code_symbols_in_file(path)` | symbols defined in a file |
|
|
337
|
+
| `code_search_symbol(query)` | BM25 search over symbol qualified names |
|
|
338
|
+
| `code_reindex(path=None)` | index/re-index the repo or a subpath |
|
|
339
|
+
|
|
340
|
+
Cross-repo bridge tools (resolve the shared `_bridge.omni` store; return `[]` /
|
|
341
|
+
an empty shape when it does not exist yet). When the current checkout is on a
|
|
342
|
+
non-default git branch with in-flight bindings, these auto-detect and read
|
|
343
|
+
that repo's bridge branch overlay instead of `main` — see
|
|
344
|
+
[docs/BRANCH_INDEXING.md § Bridge store](docs/BRANCH_INDEXING.md#bridge-store):
|
|
345
|
+
|
|
346
|
+
| Tool | Returns |
|
|
347
|
+
|------|---------|
|
|
348
|
+
| `code_interface_providers(kind, key)` | repos that **provide** a contract (`env_var`/`endpoint`/`package`/`service`) |
|
|
349
|
+
| `code_interface_consumers(kind, key)` | repos that **consume** it (`endpoint` keys are normalized from raw paths) |
|
|
350
|
+
| `code_cross_repo_impact(symbol_id)` | the symbol's own bindings + every binding for those same contracts in **other** repos |
|
|
351
|
+
| `code_interface_search(query, kind=None)` | BM25 search over interface bindings by normalized key |
|
|
352
|
+
|
|
353
|
+
## CLI
|
|
354
|
+
|
|
355
|
+
`witan-code` (cyclopts); also available as `witan code …` when witan-code is installed alongside witan:
|
|
356
|
+
|
|
357
|
+
- `setup [--dry-run]` — fetch the pinned omnigraph binary to `~/.local/bin/`
|
|
358
|
+
for standalone use (see [Install](#install)).
|
|
359
|
+
- `index [PATH]` — incremental; skips files whose content hash is unchanged.
|
|
360
|
+
- `reindex [PATH]` — force rebuild a path.
|
|
361
|
+
- `repos` — list all indexed repos with file count, symbol count, and store size.
|
|
362
|
+
- `branches [--prune]` — list omnigraph branches per store; `--prune` deletes
|
|
363
|
+
the current repo's store branches whose git branch is gone (plus
|
|
364
|
+
`_detached`). Non-default git branches index onto same-named omnigraph
|
|
365
|
+
branches so in-flight work never overwrites the shared `main` view — see
|
|
366
|
+
[docs/BRANCH_INDEXING.md](docs/BRANCH_INDEXING.md).
|
|
367
|
+
- `deps [--kind K] [--repo SUBSTR] [--html PATH] [--open-browser]` —
|
|
368
|
+
visualize cross-repo dependencies from the bridge store. Prints a Rich
|
|
369
|
+
summary of "repo A depends on repo B" links (A consumes a contract B
|
|
370
|
+
provides; for `service`, the deploying repo depends on what it deploys) and,
|
|
371
|
+
with `--html`, writes a self-contained interactive force-directed graph
|
|
372
|
+
(click an edge to list the individual linkages — env vars, endpoints,
|
|
373
|
+
packages, deployed repos — behind it). Defaults to a repos-only view;
|
|
374
|
+
`--kind` filters to one contract kind and `--repo` keeps only links touching
|
|
375
|
+
a matching repo.
|
|
376
|
+
- `session-init` — seed/refresh the whole repo's code graph in the background
|
|
377
|
+
(see [Hooks](#hooks)); registered as the `SessionStart` hook, not usually
|
|
378
|
+
run by hand.
|
|
379
|
+
- `reindex-hook` — incrementally reindex the file named in stdin's hook JSON
|
|
380
|
+
(see [Hooks](#hooks)); registered as the `PostToolUse` hook, not usually
|
|
381
|
+
run by hand.
|
|
382
|
+
- `inject-context` — print the `UserPromptSubmit` status block (see
|
|
383
|
+
[Hooks](#hooks)); registered as the `UserPromptSubmit` hook, not usually
|
|
384
|
+
run by hand.
|
|
385
|
+
- `optimize [--store PATH] [--bridge]` — compact a store's Lance fragments
|
|
386
|
+
(non-destructive; see [Store compaction](#store-compaction)). Defaults to
|
|
387
|
+
the current repo's store; `--bridge` targets the shared bridge store
|
|
388
|
+
instead.
|
|
389
|
+
- `cleanup [--store PATH] [--bridge] [--keep N] [--older-than DUR] --yes` —
|
|
390
|
+
reclaim disk by GC'ing old Lance versions (**destructive**; requires
|
|
391
|
+
`--yes`).
|
|
392
|
+
- `checkpoint` — opportunistically compact the current repo's store and the
|
|
393
|
+
bridge store if due (see [Hooks](#hooks)); registered as the `Stop` hook,
|
|
394
|
+
not usually run by hand.
|
|
395
|
+
|
|
396
|
+
Both print a summary: files scanned/indexed/skipped, symbols, edges, errors. A
|
|
397
|
+
parse failure on one file logs to stderr and continues.
|
|
398
|
+
|
|
399
|
+
## Store compaction
|
|
400
|
+
|
|
401
|
+
Every `load()`/`mutate()` call (indexing) appends a new tiny Lance fragment +
|
|
402
|
+
manifest version to a store; left uncompacted, it bloats until *opening* the
|
|
403
|
+
store dominates query latency, regardless of row count — the same failure
|
|
404
|
+
mode witan's own store hit (PR #98; see
|
|
405
|
+
[`witan/witan/maintenance.py`](../witan/witan/maintenance.py)). Two
|
|
406
|
+
mechanisms keep this in check, mirroring that module (deliberately duplicated
|
|
407
|
+
— no cross-package import):
|
|
408
|
+
|
|
409
|
+
- **Opportunistic**: the `witan-code checkpoint` Stop hook spawns a
|
|
410
|
+
throttled, detached `witan-code optimize` for the current repo's store and
|
|
411
|
+
the shared bridge store, at most once per `WITAN_CODE_OPTIMIZE_INTERVAL`
|
|
412
|
+
(default daily; `0` disables) each — see [Hooks](#hooks).
|
|
413
|
+
- **Scheduled**: `witan-code optimize [--store PATH | --bridge]` /
|
|
414
|
+
`witan-code cleanup --yes` for cron/systemd-timer driven maintenance on a
|
|
415
|
+
busy store. `optimize` is non-destructive and safe to run repeatedly;
|
|
416
|
+
`cleanup` GCs old Lance versions to reclaim disk and is destructive, so it
|
|
417
|
+
requires `--yes`.
|
|
418
|
+
|
|
419
|
+
## Skill
|
|
420
|
+
|
|
421
|
+
[`witan_code/skills/witan-code/SKILL.md`](witan_code/skills/witan-code/SKILL.md)
|
|
422
|
+
is a `/witan-code` entry point covering tool selection (vs. grep/Explore), a
|
|
423
|
+
quick tool reference, and linking symbol ids into witan tasks/memories.
|
|
424
|
+
Installed automatically by `witan-code setup` (to `~/.claude/skills/`, or
|
|
425
|
+
`~/.pi/agent/skills/` under `--agent pi`); to install it manually instead,
|
|
426
|
+
symlink or copy the directory into the equivalent path for your agent.
|
|
427
|
+
|
|
428
|
+
## Hooks
|
|
429
|
+
|
|
430
|
+
Four hooks — all bare CLI commands, no wrapper scripts, so they're portable
|
|
431
|
+
to any platform witan-code installs on (Windows included — no bash/setsid
|
|
432
|
+
dependency). Named `witan-code <command>` below (standalone install via
|
|
433
|
+
`witan-code setup`); when `witan setup` folds this bundle in instead (see
|
|
434
|
+
[Install](#install)), they register as `witan code <command>` so only
|
|
435
|
+
`witan` needs to be on `PATH`. Register manually per
|
|
436
|
+
[configs/hooks/README.md](../../../configs/hooks/README.md) for either form.
|
|
437
|
+
A Pi equivalent of all four lives in one extension,
|
|
438
|
+
[`witan_code/extensions/pi/codegraph.ts`](witan_code/extensions/pi/codegraph.ts)
|
|
439
|
+
(see [configs/pi/README.md](../../../configs/pi/README.md)):
|
|
440
|
+
|
|
441
|
+
- **`witan-code session-init`** (`SessionStart`) — seeds/refreshes the whole
|
|
442
|
+
repo in the background on session start (first run builds the full index;
|
|
443
|
+
later runs re-hash and skip unchanged files). Detaches a background child
|
|
444
|
+
and returns immediately; never delays session start. A per-repo lock
|
|
445
|
+
(`${TMPDIR:-/tmp}/codegraph-init-<sha256(project_dir)[:16]>.lock`, released
|
|
446
|
+
by the detached child when it finishes) prevents overlapping sessions from
|
|
447
|
+
indexing at once — hashed rather than a sanitized path so two distinct
|
|
448
|
+
paths can't collide on the same lock and a long checkout path can't exceed
|
|
449
|
+
a filesystem's filename length limit.
|
|
450
|
+
- **`witan-code reindex-hook`** (`PostToolUse`, matcher `Edit|Write`) — reads
|
|
451
|
+
the tool payload from stdin and incrementally re-indexes the changed file
|
|
452
|
+
in the foreground (fast — one file). Best-effort — a missing/malformed
|
|
453
|
+
payload or parse failure is a silent no-op, never interrupting the agent.
|
|
454
|
+
- **`witan-code inject-context`** (`UserPromptSubmit`) — prints a short status
|
|
455
|
+
block: whether the current repo is indexed (file count, last-updated time),
|
|
456
|
+
or that a background index from `session-init` is still running (checking
|
|
457
|
+
the same lock path above), plus a nudge to prefer `code_*` tools over grep.
|
|
458
|
+
Independent of `witan`'s own `inject-context` hook (no cross-package
|
|
459
|
+
coupling) — register it alone for a witan-code-only install. Prints
|
|
460
|
+
nothing when the repo has neither a store nor an index in flight.
|
|
461
|
+
- **`witan-code checkpoint`** (`Stop`) — spawns a throttled, detached
|
|
462
|
+
`witan-code optimize` for the current repo's store and the shared bridge
|
|
463
|
+
store if either is due (see [Store compaction](#store-compaction)).
|
|
464
|
+
Independent of `witan`'s own `session-checkpoint` hook (no cross-package
|
|
465
|
+
coupling). Best-effort and non-blocking.
|
|
466
|
+
|
|
467
|
+
Manual install: register the bare commands directly under the matching event
|
|
468
|
+
in `settings.json` (see the linked README for the exact JSON) — there are no
|
|
469
|
+
scripts to symlink.
|
|
470
|
+
|
|
471
|
+
## Incremental indexing
|
|
472
|
+
|
|
473
|
+
Each `CodeFile` stores a sha256 `contentHash`. On reindex, if the hash is
|
|
474
|
+
unchanged the file is skipped. Otherwise its `Symbol`s and `CodeFile` are
|
|
475
|
+
deleted (as separate `delete.gq` calls — Omnigraph cannot mix deletes with
|
|
476
|
+
inserts in one query) and then re-parsed and re-inserted.
|