witan-code 0.3.0__tar.gz → 0.6.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 (90) hide show
  1. witan_code-0.6.0/CHANGELOG.md +97 -0
  2. witan_code-0.3.0/README.md → witan_code-0.6.0/PKG-INFO +75 -5
  3. witan_code-0.3.0/PKG-INFO → witan_code-0.6.0/README.md +45 -32
  4. {witan_code-0.3.0 → witan_code-0.6.0}/docs/CLI_REFERENCE.md +7 -1
  5. {witan_code-0.3.0 → witan_code-0.6.0}/pyproject.toml +9 -3
  6. witan_code-0.6.0/tests/test_cli_output_format.py +91 -0
  7. witan_code-0.6.0/tests/test_config.py +64 -0
  8. witan_code-0.6.0/tests/test_config_targets.py +247 -0
  9. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_elicit.py +3 -54
  10. witan_code-0.6.0/tests/test_graph.py +23 -0
  11. witan_code-0.6.0/tests/test_repo.py +87 -0
  12. witan_code-0.6.0/tests/test_setup.py +103 -0
  13. {witan_code-0.3.0 → witan_code-0.6.0}/uv.lock +148 -62
  14. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/bridge.py +5 -8
  15. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/cli.py +130 -89
  16. witan_code-0.6.0/witan_code/config.py +214 -0
  17. witan_code-0.6.0/witan_code/elicit.py +54 -0
  18. witan_code-0.6.0/witan_code/graph.py +99 -0
  19. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/hooks.py +1 -1
  20. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/indexer.py +3 -9
  21. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/maintenance.py +26 -53
  22. witan_code-0.6.0/witan_code/output.py +42 -0
  23. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/repo.py +17 -42
  24. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/schema/bridge-schema.pg +6 -2
  25. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/schema/code-schema.pg +6 -1
  26. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/server.py +3 -0
  27. witan_code-0.6.0/witan_code/setup.py +106 -0
  28. witan_code-0.3.0/CHANGELOG.md +0 -44
  29. witan_code-0.3.0/queries/code_read.gq +0 -152
  30. witan_code-0.3.0/tests/test_detach.py +0 -39
  31. witan_code-0.3.0/tests/test_graph.py +0 -39
  32. witan_code-0.3.0/tests/test_repo.py +0 -60
  33. witan_code-0.3.0/tests/test_setup.py +0 -266
  34. witan_code-0.3.0/witan_code/_detach.py +0 -27
  35. witan_code-0.3.0/witan_code/config.py +0 -65
  36. witan_code-0.3.0/witan_code/elicit.py +0 -92
  37. witan_code-0.3.0/witan_code/graph.py +0 -271
  38. witan_code-0.3.0/witan_code/queries/code_mutations.gq +0 -81
  39. witan_code-0.3.0/witan_code/setup.py +0 -230
  40. {witan_code-0.3.0 → witan_code-0.6.0}/.gitignore +0 -0
  41. {witan_code-0.3.0 → witan_code-0.6.0}/config/claude.json +0 -0
  42. {witan_code-0.3.0 → witan_code-0.6.0}/config/copilot.json +0 -0
  43. {witan_code-0.3.0 → witan_code-0.6.0}/config/pi.json +0 -0
  44. {witan_code-0.3.0 → witan_code-0.6.0}/docs/BRANCH_INDEXING.md +0 -0
  45. {witan_code-0.3.0 → witan_code-0.6.0}/docs/EDGE_PRECISION_TIERS.md +0 -0
  46. {witan_code-0.3.0 → witan_code-0.6.0}/docs/PACKAGE_MAP.md +0 -0
  47. {witan_code-0.3.0 → witan_code-0.6.0}/docs/STAGE2_STITCHING.md +0 -0
  48. {witan_code-0.3.0 → witan_code-0.6.0}/docs/SYMBOL_FORMAT.md +0 -0
  49. {witan_code-0.3.0 → witan_code-0.6.0}/docs/SYMBOL_TABLE.md +0 -0
  50. {witan_code-0.3.0 → witan_code-0.6.0}/docs/USER_GUIDE.md +0 -0
  51. {witan_code-0.3.0 → witan_code-0.6.0}/install.sh +0 -0
  52. {witan_code-0.3.0 → witan_code-0.6.0}/tests/__init__.py +0 -0
  53. {witan_code-0.3.0 → witan_code-0.6.0}/tests/conftest.py +0 -0
  54. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_branches.py +0 -0
  55. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_bridge.py +0 -0
  56. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_cli_hooks.py +0 -0
  57. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_context.py +0 -0
  58. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_crossrepo.py +0 -0
  59. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_edges.py +0 -0
  60. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_enrichment.py +0 -0
  61. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_hooks.py +0 -0
  62. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_indexer.py +0 -0
  63. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_languages.py +0 -0
  64. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_maintenance.py +0 -0
  65. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_package_map.py +0 -0
  66. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_server.py +0 -0
  67. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_stitch.py +0 -0
  68. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_symbol_table.py +0 -0
  69. {witan_code-0.3.0 → witan_code-0.6.0}/tests/test_visualize.py +0 -0
  70. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/__init__.py +0 -0
  71. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/__main__.py +0 -0
  72. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/bridge_extractors.py +0 -0
  73. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/context.py +0 -0
  74. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/edges.py +0 -0
  75. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/extensions/pi/codegraph.ts +0 -0
  76. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/package_map.py +0 -0
  77. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/queries/bridge.gq +0 -0
  78. {witan_code-0.3.0 → witan_code-0.6.0/witan_code}/queries/code_mutations.gq +0 -0
  79. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/queries/code_read.gq +0 -0
  80. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/queries/delete.gq +0 -0
  81. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/queries_ts/bash.scm +0 -0
  82. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/queries_ts/hcl.scm +0 -0
  83. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/queries_ts/python.scm +0 -0
  84. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/queries_ts/sql.scm +0 -0
  85. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/queries_ts/typescript.scm +0 -0
  86. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/queries_ts/yaml.scm +0 -0
  87. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/skills/witan-code/SKILL.md +0 -0
  88. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/stitch.py +0 -0
  89. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/store.py +0 -0
  90. {witan_code-0.3.0 → witan_code-0.6.0}/witan_code/visualize.py +0 -0
@@ -0,0 +1,97 @@
1
+ # Changelog
2
+
3
+ All notable changes to `witan-code` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/) (pre-1.0:
7
+ a MINOR bump may include breaking changes).
8
+
9
+ ## [0.6.0] - 2026-07-21
10
+
11
+ ### Fixed
12
+
13
+ - **`repo.detect(override=...)` and the `WITAN_REPO` env var now route
14
+ through `normalise`** (issue #142), same as an auto-detected git remote —
15
+ matching the fix in witan-council 0.6.0. Previously an explicitly-passed
16
+ `repo=` (or `WITAN_REPO`) was stored verbatim, bypassing
17
+ canonicalization. `graph_id` (the shared-cluster graph id) already
18
+ case-folded unconditionally and needs no change; a repo whose canonical
19
+ key changes case after this fix should be re-indexed (`witan-code
20
+ reindex`) so its per-repo store lands under the new key.
21
+ - Depends on `witan-core[cli]>=0.2,<1` (unchanged range; picks up 0.4.0's
22
+ repo-key case-fold in `normalise`, which this package's `repo.detect`
23
+ relies on).
24
+
25
+ ## [0.5.0] - 2026-07-20
26
+
27
+ ### Added
28
+
29
+ - `config.toml` + `[targets.<name>]` support (previously env-var only):
30
+ `WITAN_CONFIG` / `~/.config/witan/config.toml`, a global `code_dir`/
31
+ `author`, and named targets overriding `code_dir`/`author`, selected by
32
+ `WITAN_TARGET` env var, an explicit `load(target=...)`, or auto-detection
33
+ via `match_paths`/`match_repos`/`match_hosts`/`match_orgs` (same
34
+ precedence and file as witan — see `witan_core.target_config` and
35
+ `witan.config.load()`'s docstring). A target can carry witan's
36
+ `server`/`graph`/`token` alongside this server's `code_dir` under one
37
+ name; witan-code reads only the fields it knows. `Config` gained
38
+ `target_name`. `load()` now optionally takes a `target` argument (was
39
+ zero-argument only). New dependency: `pydantic>=2,<3`.
40
+
41
+ ### Changed
42
+
43
+ - Now depends on `witan-core[cli]>=0.2,<1` (unchanged range; picks up
44
+ 0.3.0, which adds the shared target-routing logic this release uses).
45
+
46
+ ## [0.4.0] - 2026-07-16
47
+
48
+ ### Added
49
+
50
+ - **`witan-code --output-format`**: `repos`, `symbols`, and `stitch` can now
51
+ render their table data as `json`, `toml`, or `yaml` in addition to the
52
+ default Rich table output. The same option is honored when mounted as
53
+ `witan --output-format … code …`.
54
+
55
+ ### Changed
56
+
57
+ - Adopted the shared `witan-core` package for the CLI scaffolding
58
+ (`make_app`/`report_install`/`resolve_author` + agent-name constants),
59
+ replacing the previously duplicated copies. No user-facing behavior change.
60
+ Now depends on `witan-core[cli]>=0.2,<1`.
61
+
62
+ ## [0.3.0] - 2026-07-10
63
+
64
+ ### Added
65
+
66
+ - **`witan-code --version`**: prints the installed package version,
67
+ appending a git short ref when installed editable (local workspace) or
68
+ directly from a VCS URL (`uvx --from git+...`), via the new
69
+ `agent_config_kit.resolve_version()` helper.
70
+
71
+ ### Changed
72
+
73
+ - `agent-config-kit` dependency bumped to `>=0.4,<1` (was `>=0.1,<1`) to
74
+ require the version that ships `resolve_version`, which backs the new
75
+ `--version` flag.
76
+
77
+ ## [0.2.1] - 2026-07-09
78
+
79
+ ### Fixed
80
+
81
+ - **Pinned `tree-sitter` to `0.25.x`**: `0.26.0` has a use-after-free in its
82
+ pyo3 `Node` binding that segfaults (exit 139) under the heavy
83
+ `.parent()`/`.children()` churn `_walk_defs` does on real-world-sized
84
+ files — deterministically on the 2nd-3rd parse in one process, so
85
+ `witan-code index` hard-crashed on any real repo. `0.25.0`-`0.25.2` are
86
+ crash-free with the same ABI-15 grammar wheels.
87
+
88
+ ## [0.2.0] - 2026-07-09
89
+
90
+ Initial PyPI release. `witan-code` is a tree-sitter-based code graph MCP
91
+ server (Layer 2): indexes a repo's symbols (functions, methods, classes,
92
+ modules) and their relationships, then exposes `code_search_symbol` /
93
+ `code_find_definition` / `code_find_references` / `code_callers` /
94
+ `code_impact` queries to the agent. Includes the Layer 2.5 cross-repo bridge,
95
+ linking SOA repos by shared `env_var`/`endpoint`/`package`/`service` contract
96
+ keys. Mounts standalone as `witan-code` or under `witan code …` when the
97
+ `witan` umbrella CLI is installed alongside it.
@@ -1,3 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: witan-code
3
+ Version: 0.6.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.4
14
+ Requires-Dist: cyclopts<5,>=4
15
+ Requires-Dist: fastmcp<4,>=3.4.2
16
+ Requires-Dist: pydantic<3,>=2
17
+ Requires-Dist: pyyaml<7,>=6
18
+ Requires-Dist: rich>=13
19
+ Requires-Dist: tomli-w<2,>=1
20
+ Requires-Dist: tree-sitter-bash<0.26,>=0.25
21
+ Requires-Dist: tree-sitter-go<0.26,>=0.25
22
+ Requires-Dist: tree-sitter-hcl<1.3,>=1.2
23
+ Requires-Dist: tree-sitter-python<0.26,>=0.25
24
+ Requires-Dist: tree-sitter-sql<0.4,>=0.3
25
+ Requires-Dist: tree-sitter-typescript<0.24,>=0.23
26
+ Requires-Dist: tree-sitter-yaml<0.8,>=0.7
27
+ Requires-Dist: tree-sitter<0.26,>=0.25
28
+ Requires-Dist: witan-core[cli]<1,>=0.2
29
+ Description-Content-Type: text/markdown
30
+
1
31
  # witan-code
2
32
 
3
33
  > "witan" is pronounced `WIT-ən` (/ˈwɪtən/) — rhymes with "written" minus the r.
@@ -60,6 +90,22 @@ machine's local code index.
60
90
  - [CLI reference](https://github.com/mitodl/agent-kit/blob/main/mcp/servers/witan-code/docs/CLI_REFERENCE.md) — every `witan-code` command with
61
91
  its full flag table and an example invocation.
62
92
 
93
+ ## CLI structured output
94
+
95
+ `witan-code` table-producing commands can emit machine-readable output instead
96
+ of Rich tables:
97
+
98
+ ```bash
99
+ witan-code --output-format json repos
100
+ witan-code --output-format yaml symbols --role exported
101
+ witan --output-format toml code stitch --unresolved
102
+ ```
103
+
104
+ Supported formats are `txt` (default), `json`, `toml`, and `yaml`. The same
105
+ setting is available through `WITAN_OUTPUT_FORMAT`. It currently applies to
106
+ `repos`, `symbols`, and `stitch`; free-text commands such as `index` and hook
107
+ commands keep their existing output.
108
+
63
109
  ## Cross-repo context bridge (Layer 2.5)
64
110
 
65
111
  The per-repo graph stops at a repo boundary, but service-oriented architectures
@@ -256,11 +302,12 @@ witan's). Run `witan-code setup` on its own for a witan-code-only install, or
256
302
  when witan-code isn't importable from witan's environment. See
257
303
  [Hooks](#hooks) and [Skill](#skill).
258
304
 
259
- This downloads the omnigraph release pinned by `_OMNIGRAPH_VERSION` in
260
- [`witan_code/setup.py`](./witan_code/setup.py) — the same pin `witan`'s own
261
- `setup.py` uses, kept in lockstep by Renovate (see the repo-root
262
- `renovate.json`'s `omnigraph-version` customManager, which bumps both files
263
- in one PR). There is no build-time bundling of the binary into the wheel —
305
+ This downloads the omnigraph release pinned by `_OMNIGRAPH_VERSION` in the
306
+ shared installer
307
+ [`witan_core.omnigraph_install`](../../../packages/witan-core/witan_core/omnigraph_install.py)
308
+ — the same pin `witan` uses (both import it from `witan-core`), bumped by
309
+ Renovate (see the repo-root `renovate.json`'s `omnigraph-version`
310
+ customManager). There is no build-time bundling of the binary into the wheel —
264
311
  `witan-code setup` (or `witan setup`) is the only source of the binary, and
265
312
  re-running it is how you pick up a version bump.
266
313
 
@@ -288,12 +335,35 @@ symlink alternative:
288
335
  | `WITAN_AUTHOR` / `USER` | `unknown` | attribution string |
289
336
  | `WITAN_REPO` | — | override the detected repo slug |
290
337
  | `WITAN_CODE_OPTIMIZE_INTERVAL` | `86400` (daily) | throttle window (seconds) for `checkpoint`'s opportunistic store compaction; `0` disables it |
338
+ | `WITAN_CONFIG` | `~/.config/witan/config.toml` | config file path (see below) |
339
+ | `WITAN_TARGET` | — | force a named `[targets.<name>]` block instead of auto-detecting one |
291
340
 
292
341
  The store URI for a repo is `<dir>/<sanitized-slug>.omni`, where the slug's `/`
293
342
  and `:` are replaced with `_`. The shared cross-repo bridge lives alongside them
294
343
  at `<dir>/_bridge.omni` and is created lazily on the first index that yields any
295
344
  bindings.
296
345
 
346
+ ### config.toml
347
+
348
+ witan-code reads the same `config.toml` as witan (witan-council) — a global
349
+ `code_dir`/`author`, plus named `[targets.<name>]` blocks that override them
350
+ and are selected by `WITAN_TARGET`, `load(target=...)` (Python API only — no
351
+ CLI `--target` flag yet), or auto-detection against the current
352
+ repo/checkout (`match_paths` > `match_repos` > `match_hosts` > `match_orgs`
353
+ — see witan's README/`witan/config.py` docstring for the full precedence).
354
+ Because the file is shared, one target block can carry witan's
355
+ `server`/`graph`/`token` alongside `code_dir` under
356
+ the same name — each server reads only the fields it knows:
357
+
358
+ ```toml
359
+ [targets.work]
360
+ server = "http://witan.internal:8080" # witan (witan-council)
361
+ code_dir = "/mnt/work/witan-code" # witan-code
362
+ match_orgs = ["myorg"]
363
+ ```
364
+
365
+ Environment variables always win over `config.toml`.
366
+
297
367
  ## MCP tools
298
368
 
299
369
  Each tool resolves the per-repo store from the current working directory and
@@ -1,30 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: witan-code
3
- Version: 0.3.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.4
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.26,>=0.25
26
- Description-Content-Type: text/markdown
27
-
28
1
  # witan-code
29
2
 
30
3
  > "witan" is pronounced `WIT-ən` (/ˈwɪtən/) — rhymes with "written" minus the r.
@@ -87,6 +60,22 @@ machine's local code index.
87
60
  - [CLI reference](https://github.com/mitodl/agent-kit/blob/main/mcp/servers/witan-code/docs/CLI_REFERENCE.md) — every `witan-code` command with
88
61
  its full flag table and an example invocation.
89
62
 
63
+ ## CLI structured output
64
+
65
+ `witan-code` table-producing commands can emit machine-readable output instead
66
+ of Rich tables:
67
+
68
+ ```bash
69
+ witan-code --output-format json repos
70
+ witan-code --output-format yaml symbols --role exported
71
+ witan --output-format toml code stitch --unresolved
72
+ ```
73
+
74
+ Supported formats are `txt` (default), `json`, `toml`, and `yaml`. The same
75
+ setting is available through `WITAN_OUTPUT_FORMAT`. It currently applies to
76
+ `repos`, `symbols`, and `stitch`; free-text commands such as `index` and hook
77
+ commands keep their existing output.
78
+
90
79
  ## Cross-repo context bridge (Layer 2.5)
91
80
 
92
81
  The per-repo graph stops at a repo boundary, but service-oriented architectures
@@ -283,11 +272,12 @@ witan's). Run `witan-code setup` on its own for a witan-code-only install, or
283
272
  when witan-code isn't importable from witan's environment. See
284
273
  [Hooks](#hooks) and [Skill](#skill).
285
274
 
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 —
275
+ This downloads the omnigraph release pinned by `_OMNIGRAPH_VERSION` in the
276
+ shared installer
277
+ [`witan_core.omnigraph_install`](../../../packages/witan-core/witan_core/omnigraph_install.py)
278
+ — the same pin `witan` uses (both import it from `witan-core`), bumped by
279
+ Renovate (see the repo-root `renovate.json`'s `omnigraph-version`
280
+ customManager). There is no build-time bundling of the binary into the wheel —
291
281
  `witan-code setup` (or `witan setup`) is the only source of the binary, and
292
282
  re-running it is how you pick up a version bump.
293
283
 
@@ -315,12 +305,35 @@ symlink alternative:
315
305
  | `WITAN_AUTHOR` / `USER` | `unknown` | attribution string |
316
306
  | `WITAN_REPO` | — | override the detected repo slug |
317
307
  | `WITAN_CODE_OPTIMIZE_INTERVAL` | `86400` (daily) | throttle window (seconds) for `checkpoint`'s opportunistic store compaction; `0` disables it |
308
+ | `WITAN_CONFIG` | `~/.config/witan/config.toml` | config file path (see below) |
309
+ | `WITAN_TARGET` | — | force a named `[targets.<name>]` block instead of auto-detecting one |
318
310
 
319
311
  The store URI for a repo is `<dir>/<sanitized-slug>.omni`, where the slug's `/`
320
312
  and `:` are replaced with `_`. The shared cross-repo bridge lives alongside them
321
313
  at `<dir>/_bridge.omni` and is created lazily on the first index that yields any
322
314
  bindings.
323
315
 
316
+ ### config.toml
317
+
318
+ witan-code reads the same `config.toml` as witan (witan-council) — a global
319
+ `code_dir`/`author`, plus named `[targets.<name>]` blocks that override them
320
+ and are selected by `WITAN_TARGET`, `load(target=...)` (Python API only — no
321
+ CLI `--target` flag yet), or auto-detection against the current
322
+ repo/checkout (`match_paths` > `match_repos` > `match_hosts` > `match_orgs`
323
+ — see witan's README/`witan/config.py` docstring for the full precedence).
324
+ Because the file is shared, one target block can carry witan's
325
+ `server`/`graph`/`token` alongside `code_dir` under
326
+ the same name — each server reads only the fields it knows:
327
+
328
+ ```toml
329
+ [targets.work]
330
+ server = "http://witan.internal:8080" # witan (witan-council)
331
+ code_dir = "/mnt/work/witan-code" # witan-code
332
+ match_orgs = ["myorg"]
333
+ ```
334
+
335
+ Environment variables always win over `config.toml`.
336
+
324
337
  ## MCP tools
325
338
 
326
339
  Each tool resolves the per-repo store from the current working directory and
@@ -7,7 +7,13 @@ the `witan` umbrella package. Run `witan-code --help` or `witan-code
7
7
  <command> --help` for the live version of this table — it is generated from
8
8
  the same docstrings.
9
9
 
10
- Global flags: `--help` / `-h`, `--version`.
10
+ Global flags: `--help` / `-h`, `--version`, `--output-format txt|json|toml|yaml`.
11
+
12
+ `--output-format` controls table-producing commands (`repos`, `symbols`, and
13
+ `stitch`) and is also available as `WITAN_OUTPUT_FORMAT`. The default `txt`
14
+ renders the existing Rich tables; `json`, `toml`, and `yaml` emit a wrapper
15
+ object with `title` and `rows` for machine parsing. The same option works when
16
+ mounted under the umbrella CLI, e.g. `witan --output-format json code repos`.
11
17
 
12
18
  Boolean flags follow cyclopts' `--flag`/`--no-flag` convention; both forms are
13
19
  always available even where only `--flag` is shown below.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "witan-code"
3
- version = "0.3.0"
3
+ version = "0.6.0"
4
4
  description = "witan-code — tree-sitter code graph + cross-repo bridge (mounts under `witan code`)"
5
5
  readme = "README.md"
6
6
  license = "BSD-3-Clause"
@@ -11,8 +11,12 @@ classifiers = [
11
11
  ]
12
12
  dependencies = [
13
13
  "agent-config-kit>=0.4,<1",
14
+ "witan-core[cli]>=0.2,<1",
14
15
  "fastmcp>=3.4.2,<4",
15
16
  "cyclopts>=4,<5",
17
+ # [targets.<name>] override models (config.py) — also transitively pulled
18
+ # in by fastmcp, declared explicitly since config.py imports it directly.
19
+ "pydantic>=2,<3",
16
20
  # Pinned to 0.25.x: tree-sitter 0.26.0 has a use-after-free in its pyo3 Node
17
21
  # binding that segfaults (exit 139) under the heavy .parent()/.children()
18
22
  # churn _walk_defs does on real-world-sized files — deterministically on the
@@ -35,6 +39,7 @@ dependencies = [
35
39
  "tree-sitter-hcl>=1.2,<1.3",
36
40
  "tree-sitter-go>=0.25,<0.26",
37
41
  "pyyaml>=6,<7",
42
+ "tomli-w>=1,<2",
38
43
  "rich>=13",
39
44
  ]
40
45
 
@@ -56,7 +61,8 @@ build-backend = "hatchling.build"
56
61
  # agent-config-kit version bumped alongside it — even before that version
57
62
  # has actually landed on PyPI. Published wheels only see the PyPI-range
58
63
  # dependency declared above.
59
- agent-config-kit = { path = "../../../packages/agent-config-kit", editable = true }
64
+ agent-config-kit = { workspace = true }
65
+ witan-core = { workspace = true }
60
66
 
61
67
  [dependency-groups]
62
68
  test = ["pytest>=8"]
@@ -69,7 +75,7 @@ testpaths = ["tests"]
69
75
  packages = ["witan_code"]
70
76
 
71
77
  [tool.bumpversion]
72
- current_version = "0.3.0"
78
+ current_version = "0.6.0"
73
79
  allow_dirty = true
74
80
 
75
81
  [[tool.bumpversion.files]]
@@ -0,0 +1,91 @@
1
+ """Tests for the global ``--output-format`` option."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ import pytest
8
+
9
+ from witan_code import cli as cli_module
10
+ from witan_code import output as output_module
11
+
12
+
13
+ @pytest.fixture(autouse=True)
14
+ def _reset_output_format():
15
+ """Every test starts and ends on the txt default regardless of order."""
16
+ output_module.set_output_format("txt")
17
+ yield
18
+ output_module.set_output_format("txt")
19
+
20
+
21
+ def test_render_table_json_dumps_normalized_rows(capsys):
22
+ output_module.set_output_format("json")
23
+
24
+ cli_module._render_table(
25
+ title="Indexed repositories",
26
+ columns=["repo", "files"],
27
+ rows=[{"repo": "https://github.com/test/repo", "files": None}],
28
+ )
29
+
30
+ payload = json.loads(capsys.readouterr().out)
31
+ assert payload == {
32
+ "title": "Indexed repositories",
33
+ "rows": [{"repo": "https://github.com/test/repo", "files": ""}],
34
+ }
35
+
36
+
37
+ def test_render_table_toml_dumps_normalized_rows(capsys):
38
+ import tomllib
39
+
40
+ output_module.set_output_format("toml")
41
+
42
+ cli_module._render_table(
43
+ title="Symbol table — https://github.com/test/repo",
44
+ columns=["role", "refs"],
45
+ rows=[{"role": "exported", "refs": 2}],
46
+ )
47
+
48
+ payload = tomllib.loads(capsys.readouterr().out)
49
+ assert payload["title"] == "Symbol table — https://github.com/test/repo"
50
+ assert payload["rows"] == [{"role": "exported", "refs": 2}]
51
+
52
+
53
+ def test_repos_honors_structured_output(tmp_path, monkeypatch, capsys):
54
+ code_dir = tmp_path / "code"
55
+ code_dir.mkdir()
56
+ store = code_dir / "https_github.com_test_repo.omni"
57
+ store.mkdir()
58
+ (code_dir / f"{store.name}.repo").write_text("https://github.com/test/repo")
59
+ monkeypatch.setenv("WITAN_CODE_DIR", str(code_dir))
60
+ monkeypatch.setattr(
61
+ cli_module,
62
+ "_code_store_stats",
63
+ lambda store: ("https://github.com/test/repo", "7"),
64
+ )
65
+ monkeypatch.setattr(cli_module, "_dir_stats", lambda path: (1024, "2026-07-13"))
66
+ output_module.set_output_format("json")
67
+
68
+ cli_module.repos()
69
+
70
+ payload = json.loads(capsys.readouterr().out)
71
+ assert payload == {
72
+ "title": "Indexed repositories",
73
+ "rows": [
74
+ {
75
+ "repo": "https://github.com/test/repo",
76
+ "files": "7",
77
+ "size": "1.0KB",
78
+ "last indexed": "2026-07-13",
79
+ }
80
+ ],
81
+ }
82
+
83
+
84
+ def test_launcher_sets_output_format_and_forwards_tokens(monkeypatch):
85
+ calls = []
86
+ monkeypatch.setattr(cli_module, "app", lambda tokens: calls.append(tokens))
87
+
88
+ cli_module._launcher("repos", output_format="yaml")
89
+
90
+ assert output_module.get_output_format() == "yaml"
91
+ assert calls == [("repos",)]
@@ -0,0 +1,64 @@
1
+ """Canonical cluster graph-id derivation (config.graph_id).
2
+
3
+ This id is a cross-repo contract: witan-code addresses `--graph <id>` and
4
+ ol-infrastructure's cluster.yaml provisioning declares the same `<id>`. If this
5
+ algorithm drifts from the provisioning side, clients address graphs the cluster
6
+ never created — so pin the observable mapping here.
7
+ """
8
+
9
+ from witan_code import config
10
+
11
+
12
+ def test_graph_id_canonical_example():
13
+ # The example from the deployment decision; must be byte-for-byte stable.
14
+ assert (
15
+ config.graph_id("https://github.com/mitodl/ol-django")
16
+ == "code-github-com-mitodl-ol-django"
17
+ )
18
+
19
+
20
+ def test_graph_id_matches_constraint():
21
+ for repo in (
22
+ "https://github.com/mitodl/ol-django",
23
+ "http://example.com/x",
24
+ "git@github.com:mitodl/agent-kit.git",
25
+ "github.com/mitodl/ol-infrastructure",
26
+ "https://github.com/MITODL/Mixed_Case.Repo",
27
+ ):
28
+ assert config.GRAPH_ID_RE.match(config.graph_id(repo)), repo
29
+
30
+
31
+ def test_graph_id_no_underscores_and_lowercase():
32
+ gid = config.graph_id("https://github.com/MITODL/Mixed_Case.Repo")
33
+ assert "_" not in gid
34
+ assert gid == gid.lower()
35
+
36
+
37
+ def test_graph_id_strips_scheme():
38
+ with_scheme = config.graph_id("https://github.com/mitodl/x")
39
+ without = config.graph_id("github.com/mitodl/x")
40
+ assert with_scheme == without == "code-github-com-mitodl-x"
41
+
42
+
43
+ def test_graph_id_is_prefixed():
44
+ assert config.graph_id("github.com/a/b").startswith(config.CODE_GRAPH_PREFIX)
45
+
46
+
47
+ def test_graph_id_long_repo_truncates_with_hash():
48
+ long_repo = "https://github.com/mitodl/" + "a" * 200
49
+ gid = config.graph_id(long_repo)
50
+ assert len(gid) <= 64
51
+ assert config.GRAPH_ID_RE.match(gid)
52
+ # Distinct long repos disambiguate via the URI hash, never colliding.
53
+ other = config.graph_id("https://github.com/mitodl/" + "a" * 199 + "b")
54
+ assert gid != other
55
+
56
+
57
+ def test_graph_id_is_deterministic():
58
+ repo = "https://github.com/mitodl/" + "z" * 100
59
+ assert config.graph_id(repo) == config.graph_id(repo)
60
+
61
+
62
+ def test_bridge_graph_id_constant():
63
+ assert config.BRIDGE_GRAPH_ID == "code-bridge"
64
+ assert config.GRAPH_ID_RE.match(config.BRIDGE_GRAPH_ID)