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.
Files changed (77) hide show
  1. witan_code-0.2.0/.gitignore +38 -0
  2. witan_code-0.2.0/PKG-INFO +476 -0
  3. witan_code-0.2.0/README.md +449 -0
  4. witan_code-0.2.0/config/claude.json +16 -0
  5. witan_code-0.2.0/config/copilot.json +17 -0
  6. witan_code-0.2.0/config/pi.json +16 -0
  7. witan_code-0.2.0/docs/BRANCH_INDEXING.md +123 -0
  8. witan_code-0.2.0/docs/CLI_REFERENCE.md +153 -0
  9. witan_code-0.2.0/docs/EDGE_PRECISION_TIERS.md +104 -0
  10. witan_code-0.2.0/docs/PACKAGE_MAP.md +105 -0
  11. witan_code-0.2.0/docs/STAGE2_STITCHING.md +80 -0
  12. witan_code-0.2.0/docs/SYMBOL_FORMAT.md +142 -0
  13. witan_code-0.2.0/docs/SYMBOL_TABLE.md +105 -0
  14. witan_code-0.2.0/docs/USER_GUIDE.md +230 -0
  15. witan_code-0.2.0/install.sh +49 -0
  16. witan_code-0.2.0/pyproject.toml +65 -0
  17. witan_code-0.2.0/queries/code_mutations.gq +81 -0
  18. witan_code-0.2.0/queries/code_read.gq +152 -0
  19. witan_code-0.2.0/tests/__init__.py +0 -0
  20. witan_code-0.2.0/tests/conftest.py +53 -0
  21. witan_code-0.2.0/tests/test_branches.py +330 -0
  22. witan_code-0.2.0/tests/test_bridge.py +619 -0
  23. witan_code-0.2.0/tests/test_cli_hooks.py +47 -0
  24. witan_code-0.2.0/tests/test_context.py +149 -0
  25. witan_code-0.2.0/tests/test_crossrepo.py +77 -0
  26. witan_code-0.2.0/tests/test_detach.py +39 -0
  27. witan_code-0.2.0/tests/test_edges.py +397 -0
  28. witan_code-0.2.0/tests/test_elicit.py +458 -0
  29. witan_code-0.2.0/tests/test_enrichment.py +79 -0
  30. witan_code-0.2.0/tests/test_graph.py +39 -0
  31. witan_code-0.2.0/tests/test_hooks.py +188 -0
  32. witan_code-0.2.0/tests/test_indexer.py +48 -0
  33. witan_code-0.2.0/tests/test_languages.py +124 -0
  34. witan_code-0.2.0/tests/test_maintenance.py +243 -0
  35. witan_code-0.2.0/tests/test_package_map.py +136 -0
  36. witan_code-0.2.0/tests/test_repo.py +60 -0
  37. witan_code-0.2.0/tests/test_server.py +73 -0
  38. witan_code-0.2.0/tests/test_setup.py +266 -0
  39. witan_code-0.2.0/tests/test_stitch.py +404 -0
  40. witan_code-0.2.0/tests/test_symbol_table.py +342 -0
  41. witan_code-0.2.0/tests/test_visualize.py +358 -0
  42. witan_code-0.2.0/uv.lock +1694 -0
  43. witan_code-0.2.0/witan_code/__init__.py +0 -0
  44. witan_code-0.2.0/witan_code/__main__.py +4 -0
  45. witan_code-0.2.0/witan_code/_detach.py +27 -0
  46. witan_code-0.2.0/witan_code/bridge.py +392 -0
  47. witan_code-0.2.0/witan_code/bridge_extractors.py +642 -0
  48. witan_code-0.2.0/witan_code/cli.py +722 -0
  49. witan_code-0.2.0/witan_code/config.py +65 -0
  50. witan_code-0.2.0/witan_code/context.py +98 -0
  51. witan_code-0.2.0/witan_code/edges.py +171 -0
  52. witan_code-0.2.0/witan_code/elicit.py +92 -0
  53. witan_code-0.2.0/witan_code/extensions/pi/codegraph.ts +121 -0
  54. witan_code-0.2.0/witan_code/graph.py +271 -0
  55. witan_code-0.2.0/witan_code/hooks.py +116 -0
  56. witan_code-0.2.0/witan_code/indexer.py +1042 -0
  57. witan_code-0.2.0/witan_code/maintenance.py +130 -0
  58. witan_code-0.2.0/witan_code/package_map.py +74 -0
  59. witan_code-0.2.0/witan_code/queries/bridge.gq +193 -0
  60. witan_code-0.2.0/witan_code/queries/code_mutations.gq +81 -0
  61. witan_code-0.2.0/witan_code/queries/code_read.gq +154 -0
  62. witan_code-0.2.0/witan_code/queries/delete.gq +16 -0
  63. witan_code-0.2.0/witan_code/queries_ts/bash.scm +9 -0
  64. witan_code-0.2.0/witan_code/queries_ts/hcl.scm +16 -0
  65. witan_code-0.2.0/witan_code/queries_ts/python.scm +29 -0
  66. witan_code-0.2.0/witan_code/queries_ts/sql.scm +20 -0
  67. witan_code-0.2.0/witan_code/queries_ts/typescript.scm +51 -0
  68. witan_code-0.2.0/witan_code/queries_ts/yaml.scm +5 -0
  69. witan_code-0.2.0/witan_code/repo.py +282 -0
  70. witan_code-0.2.0/witan_code/schema/bridge-schema.pg +82 -0
  71. witan_code-0.2.0/witan_code/schema/code-schema.pg +51 -0
  72. witan_code-0.2.0/witan_code/server.py +859 -0
  73. witan_code-0.2.0/witan_code/setup.py +230 -0
  74. witan_code-0.2.0/witan_code/skills/witan-code/SKILL.md +97 -0
  75. witan_code-0.2.0/witan_code/stitch.py +177 -0
  76. witan_code-0.2.0/witan_code/store.py +116 -0
  77. 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.