witan-code 0.2.1__tar.gz → 0.4.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.4.0/CHANGELOG.md +60 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/PKG-INFO +26 -7
- {witan_code-0.2.1 → witan_code-0.4.0}/README.md +22 -5
- {witan_code-0.2.1 → witan_code-0.4.0}/docs/CLI_REFERENCE.md +7 -1
- {witan_code-0.2.1 → witan_code-0.4.0}/pyproject.toml +13 -3
- witan_code-0.4.0/tests/test_cli_output_format.py +91 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_elicit.py +3 -54
- witan_code-0.4.0/tests/test_graph.py +23 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_repo.py +3 -12
- witan_code-0.4.0/tests/test_setup.py +103 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/uv.lock +165 -68
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/bridge.py +5 -8
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/cli.py +130 -87
- witan_code-0.4.0/witan_code/elicit.py +54 -0
- witan_code-0.4.0/witan_code/graph.py +98 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/hooks.py +1 -1
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/indexer.py +3 -9
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/maintenance.py +26 -53
- witan_code-0.4.0/witan_code/output.py +42 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/repo.py +6 -37
- witan_code-0.4.0/witan_code/setup.py +106 -0
- witan_code-0.2.1/queries/code_read.gq +0 -152
- witan_code-0.2.1/tests/test_detach.py +0 -39
- witan_code-0.2.1/tests/test_graph.py +0 -39
- witan_code-0.2.1/tests/test_setup.py +0 -266
- witan_code-0.2.1/witan_code/_detach.py +0 -27
- witan_code-0.2.1/witan_code/elicit.py +0 -92
- witan_code-0.2.1/witan_code/graph.py +0 -271
- witan_code-0.2.1/witan_code/queries/code_mutations.gq +0 -81
- witan_code-0.2.1/witan_code/setup.py +0 -230
- {witan_code-0.2.1 → witan_code-0.4.0}/.gitignore +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/config/claude.json +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/config/copilot.json +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/config/pi.json +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/docs/BRANCH_INDEXING.md +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/docs/EDGE_PRECISION_TIERS.md +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/docs/PACKAGE_MAP.md +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/docs/STAGE2_STITCHING.md +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/docs/SYMBOL_FORMAT.md +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/docs/SYMBOL_TABLE.md +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/docs/USER_GUIDE.md +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/install.sh +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/__init__.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/conftest.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_branches.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_bridge.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_cli_hooks.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_context.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_crossrepo.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_edges.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_enrichment.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_hooks.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_indexer.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_languages.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_maintenance.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_package_map.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_server.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_stitch.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_symbol_table.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/tests/test_visualize.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/__init__.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/__main__.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/bridge_extractors.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/config.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/context.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/edges.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/extensions/pi/codegraph.ts +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/package_map.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/queries/bridge.gq +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0/witan_code}/queries/code_mutations.gq +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/queries/code_read.gq +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/queries/delete.gq +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/queries_ts/bash.scm +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/queries_ts/hcl.scm +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/queries_ts/python.scm +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/queries_ts/sql.scm +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/queries_ts/typescript.scm +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/queries_ts/yaml.scm +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/schema/bridge-schema.pg +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/schema/code-schema.pg +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/server.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/skills/witan-code/SKILL.md +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/stitch.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/store.py +0 -0
- {witan_code-0.2.1 → witan_code-0.4.0}/witan_code/visualize.py +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
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.4.0] - 2026-07-16
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`witan-code --output-format`**: `repos`, `symbols`, and `stitch` can now
|
|
14
|
+
render their table data as `json`, `toml`, or `yaml` in addition to the
|
|
15
|
+
default Rich table output. The same option is honored when mounted as
|
|
16
|
+
`witan --output-format … code …`.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Adopted the shared `witan-core` package for the CLI scaffolding
|
|
21
|
+
(`make_app`/`report_install`/`resolve_author` + agent-name constants),
|
|
22
|
+
replacing the previously duplicated copies. No user-facing behavior change.
|
|
23
|
+
Now depends on `witan-core[cli]>=0.2,<1`.
|
|
24
|
+
|
|
25
|
+
## [0.3.0] - 2026-07-10
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- **`witan-code --version`**: prints the installed package version,
|
|
30
|
+
appending a git short ref when installed editable (local workspace) or
|
|
31
|
+
directly from a VCS URL (`uvx --from git+...`), via the new
|
|
32
|
+
`agent_config_kit.resolve_version()` helper.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- `agent-config-kit` dependency bumped to `>=0.4,<1` (was `>=0.1,<1`) to
|
|
37
|
+
require the version that ships `resolve_version`, which backs the new
|
|
38
|
+
`--version` flag.
|
|
39
|
+
|
|
40
|
+
## [0.2.1] - 2026-07-09
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- **Pinned `tree-sitter` to `0.25.x`**: `0.26.0` has a use-after-free in its
|
|
45
|
+
pyo3 `Node` binding that segfaults (exit 139) under the heavy
|
|
46
|
+
`.parent()`/`.children()` churn `_walk_defs` does on real-world-sized
|
|
47
|
+
files — deterministically on the 2nd-3rd parse in one process, so
|
|
48
|
+
`witan-code index` hard-crashed on any real repo. `0.25.0`-`0.25.2` are
|
|
49
|
+
crash-free with the same ABI-15 grammar wheels.
|
|
50
|
+
|
|
51
|
+
## [0.2.0] - 2026-07-09
|
|
52
|
+
|
|
53
|
+
Initial PyPI release. `witan-code` is a tree-sitter-based code graph MCP
|
|
54
|
+
server (Layer 2): indexes a repo's symbols (functions, methods, classes,
|
|
55
|
+
modules) and their relationships, then exposes `code_search_symbol` /
|
|
56
|
+
`code_find_definition` / `code_find_references` / `code_callers` /
|
|
57
|
+
`code_impact` queries to the agent. Includes the Layer 2.5 cross-repo bridge,
|
|
58
|
+
linking SOA repos by shared `env_var`/`endpoint`/`package`/`service` contract
|
|
59
|
+
keys. Mounts standalone as `witan-code` or under `witan code …` when the
|
|
60
|
+
`witan` umbrella CLI is installed alongside it.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: witan-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: witan-code — tree-sitter code graph + cross-repo bridge (mounts under `witan code`)
|
|
5
5
|
Project-URL: Homepage, https://github.com/mitodl/agent-kit/tree/main/mcp/servers/witan-code
|
|
6
6
|
Project-URL: Repository, https://github.com/mitodl/agent-kit
|
|
@@ -10,11 +10,12 @@ License-Expression: BSD-3-Clause
|
|
|
10
10
|
Keywords: agent,code-graph,coding-agent,mcp,static-analysis,tree-sitter
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Requires-Python: >=3.11
|
|
13
|
-
Requires-Dist: agent-config-kit<1,>=0.
|
|
13
|
+
Requires-Dist: agent-config-kit<1,>=0.4
|
|
14
14
|
Requires-Dist: cyclopts<5,>=4
|
|
15
15
|
Requires-Dist: fastmcp<4,>=3.4.2
|
|
16
16
|
Requires-Dist: pyyaml<7,>=6
|
|
17
17
|
Requires-Dist: rich>=13
|
|
18
|
+
Requires-Dist: tomli-w<2,>=1
|
|
18
19
|
Requires-Dist: tree-sitter-bash<0.26,>=0.25
|
|
19
20
|
Requires-Dist: tree-sitter-go<0.26,>=0.25
|
|
20
21
|
Requires-Dist: tree-sitter-hcl<1.3,>=1.2
|
|
@@ -23,6 +24,7 @@ Requires-Dist: tree-sitter-sql<0.4,>=0.3
|
|
|
23
24
|
Requires-Dist: tree-sitter-typescript<0.24,>=0.23
|
|
24
25
|
Requires-Dist: tree-sitter-yaml<0.8,>=0.7
|
|
25
26
|
Requires-Dist: tree-sitter<0.26,>=0.25
|
|
27
|
+
Requires-Dist: witan-core[cli]<1,>=0.2
|
|
26
28
|
Description-Content-Type: text/markdown
|
|
27
29
|
|
|
28
30
|
# witan-code
|
|
@@ -87,6 +89,22 @@ machine's local code index.
|
|
|
87
89
|
- [CLI reference](https://github.com/mitodl/agent-kit/blob/main/mcp/servers/witan-code/docs/CLI_REFERENCE.md) — every `witan-code` command with
|
|
88
90
|
its full flag table and an example invocation.
|
|
89
91
|
|
|
92
|
+
## CLI structured output
|
|
93
|
+
|
|
94
|
+
`witan-code` table-producing commands can emit machine-readable output instead
|
|
95
|
+
of Rich tables:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
witan-code --output-format json repos
|
|
99
|
+
witan-code --output-format yaml symbols --role exported
|
|
100
|
+
witan --output-format toml code stitch --unresolved
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Supported formats are `txt` (default), `json`, `toml`, and `yaml`. The same
|
|
104
|
+
setting is available through `WITAN_OUTPUT_FORMAT`. It currently applies to
|
|
105
|
+
`repos`, `symbols`, and `stitch`; free-text commands such as `index` and hook
|
|
106
|
+
commands keep their existing output.
|
|
107
|
+
|
|
90
108
|
## Cross-repo context bridge (Layer 2.5)
|
|
91
109
|
|
|
92
110
|
The per-repo graph stops at a repo boundary, but service-oriented architectures
|
|
@@ -283,11 +301,12 @@ witan's). Run `witan-code setup` on its own for a witan-code-only install, or
|
|
|
283
301
|
when witan-code isn't importable from witan's environment. See
|
|
284
302
|
[Hooks](#hooks) and [Skill](#skill).
|
|
285
303
|
|
|
286
|
-
This downloads the omnigraph release pinned by `_OMNIGRAPH_VERSION` in
|
|
287
|
-
|
|
288
|
-
`
|
|
289
|
-
`
|
|
290
|
-
|
|
304
|
+
This downloads the omnigraph release pinned by `_OMNIGRAPH_VERSION` in the
|
|
305
|
+
shared installer
|
|
306
|
+
[`witan_core.omnigraph_install`](../../../packages/witan-core/witan_core/omnigraph_install.py)
|
|
307
|
+
— the same pin `witan` uses (both import it from `witan-core`), bumped by
|
|
308
|
+
Renovate (see the repo-root `renovate.json`'s `omnigraph-version`
|
|
309
|
+
customManager). There is no build-time bundling of the binary into the wheel —
|
|
291
310
|
`witan-code setup` (or `witan setup`) is the only source of the binary, and
|
|
292
311
|
re-running it is how you pick up a version bump.
|
|
293
312
|
|
|
@@ -60,6 +60,22 @@ machine's local code index.
|
|
|
60
60
|
- [CLI reference](https://github.com/mitodl/agent-kit/blob/main/mcp/servers/witan-code/docs/CLI_REFERENCE.md) — every `witan-code` command with
|
|
61
61
|
its full flag table and an example invocation.
|
|
62
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
|
+
|
|
63
79
|
## Cross-repo context bridge (Layer 2.5)
|
|
64
80
|
|
|
65
81
|
The per-repo graph stops at a repo boundary, but service-oriented architectures
|
|
@@ -256,11 +272,12 @@ witan's). Run `witan-code setup` on its own for a witan-code-only install, or
|
|
|
256
272
|
when witan-code isn't importable from witan's environment. See
|
|
257
273
|
[Hooks](#hooks) and [Skill](#skill).
|
|
258
274
|
|
|
259
|
-
This downloads the omnigraph release pinned by `_OMNIGRAPH_VERSION` in
|
|
260
|
-
|
|
261
|
-
`
|
|
262
|
-
`
|
|
263
|
-
|
|
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 —
|
|
264
281
|
`witan-code setup` (or `witan setup`) is the only source of the binary, and
|
|
265
282
|
re-running it is how you pick up a version bump.
|
|
266
283
|
|
|
@@ -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
|
+
version = "0.4.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"
|
|
@@ -10,7 +10,8 @@ classifiers = [
|
|
|
10
10
|
"Programming Language :: Python :: 3",
|
|
11
11
|
]
|
|
12
12
|
dependencies = [
|
|
13
|
-
"agent-config-kit>=0.
|
|
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",
|
|
16
17
|
# Pinned to 0.25.x: tree-sitter 0.26.0 has a use-after-free in its pyo3 Node
|
|
@@ -35,6 +36,7 @@ dependencies = [
|
|
|
35
36
|
"tree-sitter-hcl>=1.2,<1.3",
|
|
36
37
|
"tree-sitter-go>=0.25,<0.26",
|
|
37
38
|
"pyyaml>=6,<7",
|
|
39
|
+
"tomli-w>=1,<2",
|
|
38
40
|
"rich>=13",
|
|
39
41
|
]
|
|
40
42
|
|
|
@@ -51,6 +53,14 @@ witan-code = "witan_code.cli:cli"
|
|
|
51
53
|
requires = ["hatchling"]
|
|
52
54
|
build-backend = "hatchling.build"
|
|
53
55
|
|
|
56
|
+
[tool.uv.sources]
|
|
57
|
+
# Dev/CI use the in-repo workspace copy so this always tests against the
|
|
58
|
+
# agent-config-kit version bumped alongside it — even before that version
|
|
59
|
+
# has actually landed on PyPI. Published wheels only see the PyPI-range
|
|
60
|
+
# dependency declared above.
|
|
61
|
+
agent-config-kit = { path = "../../../packages/agent-config-kit", editable = true }
|
|
62
|
+
witan-core = { path = "../../../packages/witan-core", editable = true }
|
|
63
|
+
|
|
54
64
|
[dependency-groups]
|
|
55
65
|
test = ["pytest>=8"]
|
|
56
66
|
|
|
@@ -62,7 +72,7 @@ testpaths = ["tests"]
|
|
|
62
72
|
packages = ["witan_code"]
|
|
63
73
|
|
|
64
74
|
[tool.bumpversion]
|
|
65
|
-
current_version = "0.
|
|
75
|
+
current_version = "0.4.0"
|
|
66
76
|
allow_dirty = true
|
|
67
77
|
|
|
68
78
|
[[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",)]
|
|
@@ -383,60 +383,9 @@ def test_find_definition_multi_repo_disambiguation(tmp_path, monkeypatch):
|
|
|
383
383
|
assert {d["repo"] for d in r1} == {RA}
|
|
384
384
|
|
|
385
385
|
|
|
386
|
-
# ──
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
class _RaiseCtx:
|
|
390
|
-
async def elicit(self, *args, **kwargs):
|
|
391
|
-
raise RuntimeError("unsupported")
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
def test_confirm_no_ctx_or_error_returns_default():
|
|
395
|
-
from witan_code import elicit
|
|
396
|
-
|
|
397
|
-
assert (
|
|
398
|
-
asyncio.run(elicit.confirm(None, "q?", default_when_unsupported=True)) is True
|
|
399
|
-
)
|
|
400
|
-
assert (
|
|
401
|
-
asyncio.run(elicit.confirm(None, "q?", default_when_unsupported=False)) is False
|
|
402
|
-
)
|
|
403
|
-
assert (
|
|
404
|
-
asyncio.run(elicit.confirm(_RaiseCtx(), "q?", default_when_unsupported=True))
|
|
405
|
-
is True
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
def test_confirm_accept_and_decline():
|
|
410
|
-
from witan_code import elicit
|
|
411
|
-
|
|
412
|
-
assert (
|
|
413
|
-
asyncio.run(
|
|
414
|
-
elicit.confirm(_AcceptCtx(True), "q?", default_when_unsupported=False)
|
|
415
|
-
)
|
|
416
|
-
is True
|
|
417
|
-
)
|
|
418
|
-
# accepting with a False value is still a "no"
|
|
419
|
-
assert (
|
|
420
|
-
asyncio.run(
|
|
421
|
-
elicit.confirm(_AcceptCtx(False), "q?", default_when_unsupported=True)
|
|
422
|
-
)
|
|
423
|
-
is False
|
|
424
|
-
)
|
|
425
|
-
assert (
|
|
426
|
-
asyncio.run(elicit.confirm(_DeclineCtx(), "q?", default_when_unsupported=True))
|
|
427
|
-
is False
|
|
428
|
-
)
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
def test_text_no_ctx_error_or_empty_returns_default():
|
|
432
|
-
from witan_code import elicit
|
|
433
|
-
|
|
434
|
-
assert asyncio.run(elicit.text(None, "q?", default="d")) == "d"
|
|
435
|
-
assert asyncio.run(elicit.text(_RaiseCtx(), "q?", default="d")) == "d"
|
|
436
|
-
assert asyncio.run(elicit.text(_AcceptCtx(""), "q?", default="d")) == "d"
|
|
437
|
-
# whitespace-only is treated as empty -> default; a real value is stripped
|
|
438
|
-
assert asyncio.run(elicit.text(_AcceptCtx(" "), "q?", default="d")) == "d"
|
|
439
|
-
assert asyncio.run(elicit.text(_AcceptCtx(" real "), "q?", default="d")) == "real"
|
|
386
|
+
# ── choose_repo unit tests (no server/omnigraph needed) ──
|
|
387
|
+
# The confirm/text primitives are covered in witan-core's own test suite
|
|
388
|
+
# (packages/witan-core/tests/test_elicit.py); only choose_repo is witan-code's.
|
|
440
389
|
|
|
441
390
|
|
|
442
391
|
def test_choose_repo_exact_match_case_insensitive_and_stripped():
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""witan-code-subclass-specific OmnigraphClient tests.
|
|
2
|
+
|
|
3
|
+
The generic base machinery (_find_binary lookup order, OCC conflict surfacing,
|
|
4
|
+
admission-cap backoff) is covered in packages/witan-core/tests/test_omnigraph.py.
|
|
5
|
+
Here we only assert witan-code's own subclass tail: the setup-hint in the
|
|
6
|
+
binary-not-found message. (branch ops + bulk load are exercised against a real
|
|
7
|
+
store in test_branches.py / test_indexer.py.)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import shutil
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from witan_code.graph import OmnigraphClient
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_find_binary_message_names_witan_code_setup(tmp_path, monkeypatch):
|
|
19
|
+
monkeypatch.setattr(shutil, "which", lambda name: None)
|
|
20
|
+
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
21
|
+
|
|
22
|
+
with pytest.raises(RuntimeError, match="witan-code setup"):
|
|
23
|
+
OmnigraphClient._find_binary()
|
|
@@ -8,18 +8,9 @@ import pytest
|
|
|
8
8
|
from witan_code import repo
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
("git@github.com:mitodl/ol-django.git", "https://github.com/mitodl/ol-django"),
|
|
15
|
-
("https://github.com/mitodl/ol-django", "https://github.com/mitodl/ol-django"),
|
|
16
|
-
("ssh://git@github.com/mitodl/repo.git", "https://github.com/mitodl/repo"),
|
|
17
|
-
],
|
|
18
|
-
)
|
|
19
|
-
def test_normalise_matches_memory_layer(url, expected):
|
|
20
|
-
# Must stay identical to witan.repo._normalise so symbol ids and
|
|
21
|
-
# the Layer-1 symbol_refs that point at them share one repo key.
|
|
22
|
-
assert repo._normalise(url) == expected
|
|
11
|
+
# The repo-key canonicalizer (normalise) lives in witan_core.repo_key — a single
|
|
12
|
+
# source of truth shared with the memory layer (no more "must stay identical"
|
|
13
|
+
# copy). Its golden contract table is packages/witan-core/tests/test_repo_key.py.
|
|
23
14
|
|
|
24
15
|
|
|
25
16
|
def test_detect_env_override(monkeypatch):
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Tests for witan-code's registration bundle.
|
|
2
|
+
|
|
3
|
+
Generic install-mechanics behavior (dry-run no-op, additive merge, JSON
|
|
4
|
+
skip-not-crash, hook dedup, ...) is covered by agent-config-kit's own test
|
|
5
|
+
suite (``packages/agent-config-kit/tests/``); the shared omnigraph installer is
|
|
6
|
+
covered by ``packages/witan-core/tests/test_omnigraph_install.py``. The bundle
|
|
7
|
+
tests here only assert that ``witan_code_bundle()`` + ``apply("claude", ...)``
|
|
8
|
+
produces witan-code's own MCP entry and hook commands, i.e. that the wiring is
|
|
9
|
+
correct.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from agent_config_kit import apply
|
|
16
|
+
|
|
17
|
+
from witan_code import setup
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_witan_code_bundle_registers_mcp_server_and_hooks(tmp_path, monkeypatch):
|
|
21
|
+
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
22
|
+
pkg_dir = tmp_path / "pkg"
|
|
23
|
+
pkg_dir.mkdir()
|
|
24
|
+
|
|
25
|
+
bundle = setup.witan_code_bundle(pkg_dir, "tester")
|
|
26
|
+
apply("claude", bundle)
|
|
27
|
+
|
|
28
|
+
claude_json = json.loads((tmp_path / ".claude.json").read_text())
|
|
29
|
+
entry = claude_json["mcpServers"]["witan-code"]
|
|
30
|
+
assert entry["type"] == "stdio"
|
|
31
|
+
assert entry["command"] == "uvx"
|
|
32
|
+
assert entry["env"]["WITAN_AUTHOR"] == "tester"
|
|
33
|
+
|
|
34
|
+
settings = json.loads((tmp_path / ".claude" / "settings.json").read_text())
|
|
35
|
+
session_init = [
|
|
36
|
+
h
|
|
37
|
+
for e in settings["hooks"]["SessionStart"]
|
|
38
|
+
for h in e["hooks"]
|
|
39
|
+
if h["command"] == "witan-code session-init"
|
|
40
|
+
]
|
|
41
|
+
reindex = [
|
|
42
|
+
h
|
|
43
|
+
for e in settings["hooks"]["PostToolUse"]
|
|
44
|
+
for h in e["hooks"]
|
|
45
|
+
if h["command"] == "witan-code reindex-hook"
|
|
46
|
+
]
|
|
47
|
+
context = [
|
|
48
|
+
h
|
|
49
|
+
for e in settings["hooks"]["UserPromptSubmit"]
|
|
50
|
+
for h in e["hooks"]
|
|
51
|
+
if h["command"] == "witan-code inject-context"
|
|
52
|
+
]
|
|
53
|
+
checkpoint = [
|
|
54
|
+
h
|
|
55
|
+
for e in settings["hooks"]["Stop"]
|
|
56
|
+
for h in e["hooks"]
|
|
57
|
+
if h["command"] == "witan-code checkpoint"
|
|
58
|
+
]
|
|
59
|
+
assert session_init and reindex and context and checkpoint
|
|
60
|
+
# Both prompt-path hooks carry a timeout so a hung git/store can't stall.
|
|
61
|
+
assert context[0]["timeout"] == 15
|
|
62
|
+
assert checkpoint[0]["timeout"] == 15
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_witan_code_bundle_honors_binary_override(tmp_path):
|
|
66
|
+
"""witan.cli.setup_cmd passes binary="witan code" when folding this
|
|
67
|
+
bundle into witan's own, so hooks only need `witan` on PATH — not a
|
|
68
|
+
separately installed `witan-code` binary."""
|
|
69
|
+
pkg_dir = tmp_path / "pkg"
|
|
70
|
+
pkg_dir.mkdir()
|
|
71
|
+
|
|
72
|
+
bundle = setup.witan_code_bundle(pkg_dir, "tester", binary="witan code")
|
|
73
|
+
|
|
74
|
+
commands = {h.command for h in bundle.hooks if hasattr(h, "command")}
|
|
75
|
+
assert commands == {
|
|
76
|
+
"witan code session-init",
|
|
77
|
+
"witan code reindex-hook",
|
|
78
|
+
"witan code inject-context",
|
|
79
|
+
"witan code checkpoint",
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_witan_code_bundle_includes_pi_extensions_as_plugin_hooks(tmp_path):
|
|
84
|
+
pkg_dir = tmp_path / "pkg"
|
|
85
|
+
ext_dir = pkg_dir / "extensions" / "pi"
|
|
86
|
+
ext_dir.mkdir(parents=True)
|
|
87
|
+
(ext_dir / "codegraph.ts").write_text("// stub")
|
|
88
|
+
|
|
89
|
+
bundle = setup.witan_code_bundle(pkg_dir, "tester")
|
|
90
|
+
|
|
91
|
+
plugin_hooks = [h for h in bundle.hooks if hasattr(h, "entry_path")]
|
|
92
|
+
assert any(h.entry_path.name == "codegraph.ts" for h in plugin_hooks)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_witan_code_bundle_includes_bundled_skills(tmp_path):
|
|
96
|
+
pkg_dir = tmp_path / "pkg"
|
|
97
|
+
skill_dir = pkg_dir / "skills" / "witan-code"
|
|
98
|
+
skill_dir.mkdir(parents=True)
|
|
99
|
+
(skill_dir / "SKILL.md").write_text("# witan-code")
|
|
100
|
+
|
|
101
|
+
bundle = setup.witan_code_bundle(pkg_dir, "tester")
|
|
102
|
+
|
|
103
|
+
assert any(s.name == "witan-code" for s in bundle.skills)
|