witan-code 0.23.0__tar.gz → 0.24.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.23.0 → witan_code-0.24.0}/CHANGELOG.md +26 -1
- {witan_code-0.23.0 → witan_code-0.24.0}/PKG-INFO +29 -18
- {witan_code-0.23.0 → witan_code-0.24.0}/README.md +27 -16
- {witan_code-0.23.0 → witan_code-0.24.0}/docs/PACKAGE_MAP.md +1 -1
- {witan_code-0.23.0 → witan_code-0.24.0}/docs/SYMBOL_FORMAT.md +6 -6
- {witan_code-0.23.0 → witan_code-0.24.0}/docs/SYMBOL_TABLE.md +1 -1
- {witan_code-0.23.0 → witan_code-0.24.0}/pyproject.toml +14 -5
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_cli_hooks.py +6 -3
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_context.py +22 -1
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_maintenance.py +69 -5
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_setup.py +75 -8
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/cli.py +78 -28
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/context.py +43 -11
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/extensions/pi/codegraph.ts +32 -9
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/maintenance.py +49 -6
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/skills/witan-code/SKILL.md +15 -4
- {witan_code-0.23.0 → witan_code-0.24.0}/.gitignore +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/changelog.d/README.md +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/config/claude.json +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/config/copilot.json +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/config/pi.json +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/conftest.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/docs/BRANCH_INDEXING.md +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/docs/CLI_REFERENCE.md +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/docs/EDGE_PRECISION_TIERS.md +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/docs/STAGE2_STITCHING.md +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/docs/USER_GUIDE.md +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/install.sh +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/__init__.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/conftest.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_batched_deletes.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_branches.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_bridge.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_chunked_load.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_cli_dispatch.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_cli_output_format.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_cli_selected_target.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_config.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_config_targets.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_crossrepo.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_edges.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_elicit.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_enrichment.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_github_app.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_graph.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_hooks.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_identity.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_index_failure_diagnostics.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_index_reports_its_store.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_index_requires_a_repo_key.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_indexer.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_indexer_scope.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_ingest.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_languages.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_package_map.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_reaper.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_rebuild_honours_the_repo_key.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_refusal_audit.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_remote_proxy.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_remote_store.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_repo.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_server.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_stitch.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_store.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_symbol_table.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_tasks.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_tool_schema.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_views.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/tests/test_visualize.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/__init__.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/__main__.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/bridge.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/bridge_extractors.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/config.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/edges.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/elicit.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/github_app.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/graph.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/hooks.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/identity.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/indexer.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/ingest.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/output.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/package_map.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/queries/bridge.gq +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/queries/code_mutations.gq +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/queries/code_read.gq +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/queries/delete.gq +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/queries_ts/bash.scm +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/queries_ts/hcl.scm +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/queries_ts/python.scm +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/queries_ts/sql.scm +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/queries_ts/typescript.scm +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/queries_ts/yaml.scm +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/reaper.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/remote/__init__.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/remote/oidc.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/remote/proxy.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/remote/store.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/repo.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/schema/bridge-schema.pg +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/schema/code-schema.pg +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/selected_target.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/server.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/setup.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/stitch.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/store.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/views.py +0 -0
- {witan_code-0.23.0 → witan_code-0.24.0}/witan_code/visualize.py +0 -0
|
@@ -8,6 +8,32 @@ a MINOR bump may include breaking changes).
|
|
|
8
8
|
|
|
9
9
|
<!-- scriv-insert-here -->
|
|
10
10
|
|
|
11
|
+
## [0.24.0] - 2026-10-02
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `witan-code setup --force` replaces a symlink or non-directory occupying an install destination (e.g. a stale symlink under `~/.pi/agent/extensions`). Without it, `witan-code setup` now exits 2 with the conflicting path instead of a `FileNotFoundError` traceback.
|
|
16
|
+
|
|
17
|
+
- `witan-code inject-context --client pi-builtin` renders the code-tool
|
|
18
|
+
discovery line for Pi's built-in MCP: a `codemode` script that finds the
|
|
19
|
+
`mcp__<server>__code_*` name with `searchTools()` and calls it as
|
|
20
|
+
`tools.<name>()`. The Pi extension passes it unless pi-mcp-adapter's `mcp`
|
|
21
|
+
tool is registered, in which case it still passes `--client pi`, whose
|
|
22
|
+
output is unchanged.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- The `witan-code` skill describes calling the `code_*` tools under Pi's
|
|
27
|
+
built-in MCP as well as under pi-mcp-adapter, and `witan-code setup --agent
|
|
28
|
+
pi` no longer warns that pi-mcp-adapter is missing when Pi's built-in MCP
|
|
29
|
+
will read the witan-code entry.
|
|
30
|
+
- Requires `agent-config-kit>=0.12`, which has that check; with an older one,
|
|
31
|
+
`witan-code setup --agent pi` still printed the pi-mcp-adapter warning.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- `witan-code checkpoint`'s background `optimize` now also runs `cleanup --older-than 30d` at most once per `WITAN_CODE_CLEANUP_INTERVAL` (default weekly; `0` disables). On omnigraph 0.11 `optimize` no longer reclaims the storage of branches deleted by `branches --prune` or `reap-views --apply`, and nothing ran `cleanup` unless done by hand. `witan-code optimize` gains `--cleanup-older-than` to run both in one process.
|
|
36
|
+
|
|
11
37
|
## [0.23.0] - 2026-09-28
|
|
12
38
|
|
|
13
39
|
### Added
|
|
@@ -353,7 +379,6 @@ a MINOR bump may include breaking changes).
|
|
|
353
379
|
write threw". Deliberately still non-fatal — the per-repo index succeeds and
|
|
354
380
|
is worth keeping — but "non-fatal" and "unreported" are different claims.
|
|
355
381
|
|
|
356
|
-
|
|
357
382
|
- **The test suite no longer asserts an environment.** Four tests
|
|
358
383
|
(`test_branches.py`'s three branch-view assertions and
|
|
359
384
|
`test_graph.py::test_a_shared_branch_view_needs_an_identity_to_own_it`)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: witan-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24.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,7 +10,7 @@ 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.12
|
|
14
14
|
Requires-Dist: cyclopts<6,>=4
|
|
15
15
|
Requires-Dist: fastmcp<5,>=4
|
|
16
16
|
Requires-Dist: joserfc<2,>=1.0
|
|
@@ -82,7 +82,7 @@ arbitration applies. See [`docs/BRANCH_INDEXING.md`](docs/BRANCH_INDEXING.md).
|
|
|
82
82
|
The two layers compose through **soft symbol-ID references**. A Layer-1 node
|
|
83
83
|
(e.g. a `lesson` or `agent_context`) can record symbol ids of the form:
|
|
84
84
|
|
|
85
|
-
```
|
|
85
|
+
```text
|
|
86
86
|
repo#relative/path.py::QualifiedName
|
|
87
87
|
e.g. https://github.com/mitodl/ol-django#app/svc.py::Service.run
|
|
88
88
|
```
|
|
@@ -299,11 +299,12 @@ witan-code setup --dry-run # preview without writing
|
|
|
299
299
|
witan-code setup --author "Jane Doe" # attribution (default: git config user.name)
|
|
300
300
|
```
|
|
301
301
|
|
|
302
|
-
On Pi, the MCP server entry (`~/.pi/agent/mcp.json`) is read
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
302
|
+
On Pi, the MCP server entry (`~/.pi/agent/mcp.json`) is read by Pi's
|
|
303
|
+
built-in MCP support (Pi 0.99 and later), or by the pi-mcp-adapter Pi
|
|
304
|
+
package when that is installed; confirm with `pi mcp list` or `/mcp` inside
|
|
305
|
+
Pi. `witan-code setup --agent pi` warns when Pi's settings show neither one
|
|
306
|
+
would load it (the built-in turned off with `-builtin:mcp`, or a Pi older
|
|
307
|
+
than 0.99, and no adapter declared).
|
|
307
308
|
|
|
308
309
|
If `witan` is *also* installed and witan-code is importable in that same
|
|
309
310
|
environment (e.g. via the `--with` in the `uv tool install`/MCP server's
|
|
@@ -354,6 +355,7 @@ symlink alternative:
|
|
|
354
355
|
| `WITAN_AUTHOR` / `USER` | `unknown` | attribution string |
|
|
355
356
|
| `WITAN_REPO` | — | override the detected repo slug |
|
|
356
357
|
| `WITAN_CODE_OPTIMIZE_INTERVAL` | `86400` (daily) | throttle window (seconds) for `checkpoint`'s opportunistic store compaction; `0` disables it |
|
|
358
|
+
| `WITAN_CODE_CLEANUP_INTERVAL` | `604800` (weekly) | throttle window (seconds) for the `cleanup --older-than 30d` that rides a `checkpoint` compaction; `0` disables it |
|
|
357
359
|
| `WITAN_CODE_INDEX_ROLE` | `client` | `ci` designates this process the writer of a shared graph's default-branch view. Only meaningful against a shared cluster graph; local stores are unaffected. See [Branch indexing](docs/BRANCH_INDEXING.md#who-may-write-the-shared-default-branch-view) |
|
|
358
360
|
| `WITAN_ACTOR` | derived from the `witan login` session | the identity that owns the branch views this process writes; an `act-…` id or a raw OIDC `sub`. For a non-interactive writer (CI, a maintenance job). See [Branch indexing](docs/BRANCH_INDEXING.md#per-writer-branch-views) |
|
|
359
361
|
| `WITAN_CONFIG` | `~/.config/witan/config.toml` | config file path (see below) |
|
|
@@ -640,12 +642,18 @@ mechanisms keep this in check, mirroring that module (deliberately duplicated
|
|
|
640
642
|
- **Opportunistic**: the `witan-code checkpoint` Stop hook spawns a
|
|
641
643
|
throttled, detached `witan-code optimize` for the current repo's store and
|
|
642
644
|
the shared bridge store, at most once per `WITAN_CODE_OPTIMIZE_INTERVAL`
|
|
643
|
-
(default daily; `0` disables) each — see [Hooks](#hooks).
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
`
|
|
648
|
-
|
|
645
|
+
(default daily; `0` disables) each — see [Hooks](#hooks). At most once per
|
|
646
|
+
`WITAN_CODE_CLEANUP_INTERVAL` (default weekly) the run also cleans up
|
|
647
|
+
versions older than 30 days. On omnigraph 0.11 that is the only thing that
|
|
648
|
+
reclaims the storage of branches deleted by `branches --prune` or
|
|
649
|
+
`reap-views --apply`; `optimize` no longer does.
|
|
650
|
+
- **Scheduled**: `witan-code optimize [--store PATH | --bridge]
|
|
651
|
+
[--cleanup-older-than DURATION]` / `witan-code cleanup --yes` for
|
|
652
|
+
cron/systemd-timer driven maintenance on a busy store. `optimize` alone is
|
|
653
|
+
non-destructive and safe to run repeatedly. `cleanup` GCs old Lance versions
|
|
654
|
+
to reclaim disk and is destructive, so it requires `--yes`.
|
|
655
|
+
`--cleanup-older-than` makes `optimize` run that destructive cleanup
|
|
656
|
+
afterwards with no `--yes` prompt, which is how the Stop hook calls it.
|
|
649
657
|
|
|
650
658
|
## Rebuilding an unreadable store
|
|
651
659
|
|
|
@@ -721,10 +729,13 @@ A Pi equivalent of all four lives in one extension,
|
|
|
721
729
|
can tell "no cross-repo consumers" from "no cross-repo data"), and the
|
|
722
730
|
`ToolSearch` call that makes the `code_*` tools callable when the harness
|
|
723
731
|
delivers them deferred — followed by a `code_find_definition` →
|
|
724
|
-
`code_callers`/`code_impact` call template.
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
`
|
|
732
|
+
`code_callers`/`code_impact` call template. The Pi extension swaps the
|
|
733
|
+
`ToolSearch` step for Pi's calling convention: `--client pi-builtin`, for
|
|
734
|
+
Pi's built-in MCP, gives a `codemode` script (`searchTools(...)` for the
|
|
735
|
+
exact `mcp__<server>__` name, then `tools.<name>({...})`), and
|
|
736
|
+
`--client pi`, which it passes when pi-mcp-adapter's `mcp` tool is
|
|
737
|
+
registered, gives the adapter's proxy (`mcp({ search: ... })`, then
|
|
738
|
+
`mcp({ tool: ..., args: {...} })`). The default stays Claude's.
|
|
728
739
|
Independent of `witan`'s own `inject-context` hook (no cross-package
|
|
729
740
|
coupling) — register it alone for a witan-code-only install. Prints
|
|
730
741
|
nothing when the repo has neither a store nor an index in flight.
|
|
@@ -49,7 +49,7 @@ arbitration applies. See [`docs/BRANCH_INDEXING.md`](docs/BRANCH_INDEXING.md).
|
|
|
49
49
|
The two layers compose through **soft symbol-ID references**. A Layer-1 node
|
|
50
50
|
(e.g. a `lesson` or `agent_context`) can record symbol ids of the form:
|
|
51
51
|
|
|
52
|
-
```
|
|
52
|
+
```text
|
|
53
53
|
repo#relative/path.py::QualifiedName
|
|
54
54
|
e.g. https://github.com/mitodl/ol-django#app/svc.py::Service.run
|
|
55
55
|
```
|
|
@@ -266,11 +266,12 @@ witan-code setup --dry-run # preview without writing
|
|
|
266
266
|
witan-code setup --author "Jane Doe" # attribution (default: git config user.name)
|
|
267
267
|
```
|
|
268
268
|
|
|
269
|
-
On Pi, the MCP server entry (`~/.pi/agent/mcp.json`) is read
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
269
|
+
On Pi, the MCP server entry (`~/.pi/agent/mcp.json`) is read by Pi's
|
|
270
|
+
built-in MCP support (Pi 0.99 and later), or by the pi-mcp-adapter Pi
|
|
271
|
+
package when that is installed; confirm with `pi mcp list` or `/mcp` inside
|
|
272
|
+
Pi. `witan-code setup --agent pi` warns when Pi's settings show neither one
|
|
273
|
+
would load it (the built-in turned off with `-builtin:mcp`, or a Pi older
|
|
274
|
+
than 0.99, and no adapter declared).
|
|
274
275
|
|
|
275
276
|
If `witan` is *also* installed and witan-code is importable in that same
|
|
276
277
|
environment (e.g. via the `--with` in the `uv tool install`/MCP server's
|
|
@@ -321,6 +322,7 @@ symlink alternative:
|
|
|
321
322
|
| `WITAN_AUTHOR` / `USER` | `unknown` | attribution string |
|
|
322
323
|
| `WITAN_REPO` | — | override the detected repo slug |
|
|
323
324
|
| `WITAN_CODE_OPTIMIZE_INTERVAL` | `86400` (daily) | throttle window (seconds) for `checkpoint`'s opportunistic store compaction; `0` disables it |
|
|
325
|
+
| `WITAN_CODE_CLEANUP_INTERVAL` | `604800` (weekly) | throttle window (seconds) for the `cleanup --older-than 30d` that rides a `checkpoint` compaction; `0` disables it |
|
|
324
326
|
| `WITAN_CODE_INDEX_ROLE` | `client` | `ci` designates this process the writer of a shared graph's default-branch view. Only meaningful against a shared cluster graph; local stores are unaffected. See [Branch indexing](docs/BRANCH_INDEXING.md#who-may-write-the-shared-default-branch-view) |
|
|
325
327
|
| `WITAN_ACTOR` | derived from the `witan login` session | the identity that owns the branch views this process writes; an `act-…` id or a raw OIDC `sub`. For a non-interactive writer (CI, a maintenance job). See [Branch indexing](docs/BRANCH_INDEXING.md#per-writer-branch-views) |
|
|
326
328
|
| `WITAN_CONFIG` | `~/.config/witan/config.toml` | config file path (see below) |
|
|
@@ -607,12 +609,18 @@ mechanisms keep this in check, mirroring that module (deliberately duplicated
|
|
|
607
609
|
- **Opportunistic**: the `witan-code checkpoint` Stop hook spawns a
|
|
608
610
|
throttled, detached `witan-code optimize` for the current repo's store and
|
|
609
611
|
the shared bridge store, at most once per `WITAN_CODE_OPTIMIZE_INTERVAL`
|
|
610
|
-
(default daily; `0` disables) each — see [Hooks](#hooks).
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
`
|
|
615
|
-
|
|
612
|
+
(default daily; `0` disables) each — see [Hooks](#hooks). At most once per
|
|
613
|
+
`WITAN_CODE_CLEANUP_INTERVAL` (default weekly) the run also cleans up
|
|
614
|
+
versions older than 30 days. On omnigraph 0.11 that is the only thing that
|
|
615
|
+
reclaims the storage of branches deleted by `branches --prune` or
|
|
616
|
+
`reap-views --apply`; `optimize` no longer does.
|
|
617
|
+
- **Scheduled**: `witan-code optimize [--store PATH | --bridge]
|
|
618
|
+
[--cleanup-older-than DURATION]` / `witan-code cleanup --yes` for
|
|
619
|
+
cron/systemd-timer driven maintenance on a busy store. `optimize` alone is
|
|
620
|
+
non-destructive and safe to run repeatedly. `cleanup` GCs old Lance versions
|
|
621
|
+
to reclaim disk and is destructive, so it requires `--yes`.
|
|
622
|
+
`--cleanup-older-than` makes `optimize` run that destructive cleanup
|
|
623
|
+
afterwards with no `--yes` prompt, which is how the Stop hook calls it.
|
|
616
624
|
|
|
617
625
|
## Rebuilding an unreadable store
|
|
618
626
|
|
|
@@ -688,10 +696,13 @@ A Pi equivalent of all four lives in one extension,
|
|
|
688
696
|
can tell "no cross-repo consumers" from "no cross-repo data"), and the
|
|
689
697
|
`ToolSearch` call that makes the `code_*` tools callable when the harness
|
|
690
698
|
delivers them deferred — followed by a `code_find_definition` →
|
|
691
|
-
`code_callers`/`code_impact` call template.
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
`
|
|
699
|
+
`code_callers`/`code_impact` call template. The Pi extension swaps the
|
|
700
|
+
`ToolSearch` step for Pi's calling convention: `--client pi-builtin`, for
|
|
701
|
+
Pi's built-in MCP, gives a `codemode` script (`searchTools(...)` for the
|
|
702
|
+
exact `mcp__<server>__` name, then `tools.<name>({...})`), and
|
|
703
|
+
`--client pi`, which it passes when pi-mcp-adapter's `mcp` tool is
|
|
704
|
+
registered, gives the adapter's proxy (`mcp({ search: ... })`, then
|
|
705
|
+
`mcp({ tool: ..., args: {...} })`). The default stays Claude's.
|
|
695
706
|
Independent of `witan`'s own `inject-context` hook (no cross-package
|
|
696
707
|
coupling) — register it alone for a witan-code-only install. Prints
|
|
697
708
|
nothing when the repo has neither a store nor an index in flight.
|
|
@@ -80,7 +80,7 @@ only makes it authoritative.
|
|
|
80
80
|
One `PackageMap` node per repo in the bridge store, keyed on the repo URI and
|
|
81
81
|
overwritten on every full-repo index (merge-by-slug):
|
|
82
82
|
|
|
83
|
-
```
|
|
83
|
+
```text
|
|
84
84
|
node PackageMap {
|
|
85
85
|
slug: String @key // canonical repo URI
|
|
86
86
|
repo: String @index
|
|
@@ -13,7 +13,7 @@ the `:CALLS/heuristic` fallback.
|
|
|
13
13
|
|
|
14
14
|
## Format
|
|
15
15
|
|
|
16
|
-
```
|
|
16
|
+
```text
|
|
17
17
|
{scheme}:{manager}:{package}:{version}:{descriptor}
|
|
18
18
|
```
|
|
19
19
|
|
|
@@ -35,7 +35,7 @@ never encoded (they are terminal).
|
|
|
35
35
|
|
|
36
36
|
### `endpoint` → scheme `http`
|
|
37
37
|
|
|
38
|
-
```
|
|
38
|
+
```text
|
|
39
39
|
{METHOD} {normalized-path}
|
|
40
40
|
```
|
|
41
41
|
|
|
@@ -49,7 +49,7 @@ never encoded (they are terminal).
|
|
|
49
49
|
representation, not the resource, and no consumer/provider pair would agree
|
|
50
50
|
on them syntactically.
|
|
51
51
|
|
|
52
|
-
```
|
|
52
|
+
```text
|
|
53
53
|
provider: http:pypi:mit-learn:main:GET /api/v0/users/me
|
|
54
54
|
consumer: http:.:.:.:* /api/v0/users/me
|
|
55
55
|
```
|
|
@@ -58,7 +58,7 @@ consumer: http:.:.:.:* /api/v0/users/me
|
|
|
58
58
|
|
|
59
59
|
Descriptor is the variable name, verbatim.
|
|
60
60
|
|
|
61
|
-
```
|
|
61
|
+
```text
|
|
62
62
|
provider: env:.:mit-learn:main:MITOL_APP_BASE_URL
|
|
63
63
|
consumer: env:.:.:.:MITOL_APP_BASE_URL
|
|
64
64
|
```
|
|
@@ -69,7 +69,7 @@ The symbol identifies the package itself; the descriptor is `.`. Manager and
|
|
|
69
69
|
package name come from the import site (consumer) or the publishing repo's
|
|
70
70
|
package map / `package.json` (provider).
|
|
71
71
|
|
|
72
|
-
```
|
|
72
|
+
```text
|
|
73
73
|
provider: pkg:npm:@mitodl/course-search-utils:main:.
|
|
74
74
|
consumer: pkg:npm:@mitodl/course-search-utils:.:.
|
|
75
75
|
```
|
|
@@ -78,7 +78,7 @@ consumer: pkg:npm:@mitodl/course-search-utils:.:.
|
|
|
78
78
|
|
|
79
79
|
Descriptor is `{sub_kind}/{key_norm}` (sub_kind: `repo` | `image` | `name`).
|
|
80
80
|
|
|
81
|
-
```
|
|
81
|
+
```text
|
|
82
82
|
provider: svc:.:ol-infrastructure:main:repo/https://github.com/mitodl/mit-learn
|
|
83
83
|
```
|
|
84
84
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "witan-code"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.24.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"
|
|
@@ -18,10 +18,19 @@ dependencies = [
|
|
|
18
18
|
# agent-config-kit by path — except there is NO `check-core-floor`
|
|
19
19
|
# equivalent for this dependency, so this line is the only guard.
|
|
20
20
|
# >=0.10: `witan-code setup`'s install report promises a warning when Pi
|
|
21
|
-
#
|
|
21
|
+
# cannot load MCP servers, which comes from `InstallResult.prerequisites`,
|
|
22
22
|
# added in 0.10.0. witan_core's `report_install` reads it with getattr, so
|
|
23
23
|
# an older resolution does not fail — it just silently drops the warning.
|
|
24
|
-
|
|
24
|
+
# >=0.11.1: `setup --force` passes `force` through to apply(), whose
|
|
25
|
+
# symlink guard (ConflictingPathError instead of FileNotFoundError) is
|
|
26
|
+
# new in 0.11.1. Older resolutions accept the flag but still crash on a
|
|
27
|
+
# dangling symlink. The workspace resolves by path, so no test here
|
|
28
|
+
# catches a stale floor.
|
|
29
|
+
# >=0.12: `witan-code setup --agent pi` only stops warning that
|
|
30
|
+
# pi-mcp-adapter is missing when Pi's built-in MCP will load the entry
|
|
31
|
+
# from 0.12.0. Older resolutions import and run, and print the false
|
|
32
|
+
# warning.
|
|
33
|
+
"agent-config-kit>=0.12,<1",
|
|
25
34
|
# `remote`: the ADR-0005 client stack (OIDC device grant + MCP-client
|
|
26
35
|
# proxy) behind `witan-code login` and the read commands' remote mode.
|
|
27
36
|
# Costs nothing extra in practice — fastmcp is already required here and
|
|
@@ -157,7 +166,7 @@ dependencies = [
|
|
|
157
166
|
# 0.38 its text never reaches the `mcp.tool_call` log line.
|
|
158
167
|
#
|
|
159
168
|
# ★ >=0.40 IS A BEHAVIOUR FLOOR too: `witan-code setup --agent pi` promises
|
|
160
|
-
# a warning when Pi
|
|
169
|
+
# a warning when Pi cannot load MCP servers, and
|
|
161
170
|
# `witan_core.cli.report_install` only prints `InstallResult.prerequisites`
|
|
162
171
|
# from 0.40.0. It reads the field with getattr, so 0.39 imports and runs —
|
|
163
172
|
# and silently drops the warning.
|
|
@@ -244,7 +253,7 @@ testpaths = ["tests"]
|
|
|
244
253
|
packages = ["witan_code"]
|
|
245
254
|
|
|
246
255
|
[tool.bumpversion]
|
|
247
|
-
current_version = "0.
|
|
256
|
+
current_version = "0.24.0"
|
|
248
257
|
allow_dirty = true
|
|
249
258
|
|
|
250
259
|
[[tool.bumpversion.files]]
|
|
@@ -5,6 +5,8 @@ exception escape (the hooks are documented as "always exits 0"), and must
|
|
|
5
5
|
not print an extra trailing newline beyond what the backend already returns.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
8
10
|
from witan_code import cli
|
|
9
11
|
|
|
10
12
|
|
|
@@ -62,7 +64,8 @@ def test_inject_context_cmd_defaults_to_the_claude_client(monkeypatch, capsys):
|
|
|
62
64
|
assert seen == ["claude"]
|
|
63
65
|
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
@pytest.mark.parametrize("client", ["pi", "pi-builtin"])
|
|
68
|
+
def test_inject_context_cmd_passes_an_explicit_pi_client_through(monkeypatch, client):
|
|
66
69
|
import witan_code.context as context_module
|
|
67
70
|
|
|
68
71
|
seen = []
|
|
@@ -70,7 +73,7 @@ def test_inject_context_cmd_passes_an_explicit_pi_client_through(monkeypatch):
|
|
|
70
73
|
context_module, "inject_context", lambda client: seen.append(client) or ""
|
|
71
74
|
)
|
|
72
75
|
|
|
73
|
-
command, bound, _ = cli.app.parse_args(["inject-context", "--client",
|
|
76
|
+
command, bound, _ = cli.app.parse_args(["inject-context", "--client", client])
|
|
74
77
|
command(*bound.args, **bound.kwargs)
|
|
75
78
|
|
|
76
|
-
assert seen == [
|
|
79
|
+
assert seen == [client]
|
|
@@ -417,10 +417,31 @@ def test_inject_context_default_and_explicit_claude_render_the_same(
|
|
|
417
417
|
assert "`/witan-code`" in default
|
|
418
418
|
|
|
419
419
|
|
|
420
|
+
def test_inject_context_for_pi_builtin_names_a_codemode_script(tmp_path, monkeypatch):
|
|
421
|
+
"""Pi's built-in MCP declares no MCP tool to the model by default: a
|
|
422
|
+
`codemode` script finds them with `searchTools()` and calls them as
|
|
423
|
+
`tools.<name>()`. The `mcp__<server>__` prefix depends on the user's
|
|
424
|
+
config, so the block must search first and then call the returned name.
|
|
425
|
+
"""
|
|
426
|
+
_indexed(tmp_path, monkeypatch)
|
|
427
|
+
|
|
428
|
+
text = context.inject_context(client="pi-builtin")
|
|
429
|
+
|
|
430
|
+
assert "ToolSearch" not in text
|
|
431
|
+
assert "mcp({" not in text
|
|
432
|
+
assert "`codemode` script" in text
|
|
433
|
+
assert 'return await searchTools("code_find_definition callers impact")' in text
|
|
434
|
+
assert 'return await tools.<name the search returned>({ name: "X" })' in text
|
|
435
|
+
assert "/skill:witan-code" in text
|
|
436
|
+
# The status lines above the discovery line are client-neutral.
|
|
437
|
+
assert "https://github.com/test/cg" in text
|
|
438
|
+
assert "3 files" in text
|
|
439
|
+
|
|
440
|
+
|
|
420
441
|
def test_inject_context_for_pi_names_the_mcp_proxy_not_toolsearch(
|
|
421
442
|
tmp_path, monkeypatch
|
|
422
443
|
):
|
|
423
|
-
"""
|
|
444
|
+
"""With pi-mcp-adapter (`--client pi`), MCP tools sit behind its `mcp`.
|
|
424
445
|
|
|
425
446
|
The adapter prefixes each tool with its server name, and that prefix
|
|
426
447
|
depends on the user's config, so the block must search first and then
|
|
@@ -70,16 +70,70 @@ def test_spawn_background_optimize_throttles(monkeypatch, tmp_path):
|
|
|
70
70
|
monkeypatch.setattr(maintenance.subprocess, "Popen", _FakePopen)
|
|
71
71
|
|
|
72
72
|
# first call spawns and stamps; second (within window) is throttled
|
|
73
|
-
assert maintenance.spawn_background_optimize(store, now=
|
|
74
|
-
assert maintenance.spawn_background_optimize(store, now=
|
|
73
|
+
assert maintenance.spawn_background_optimize(store, now=1_000_000.0) is True
|
|
74
|
+
assert maintenance.spawn_background_optimize(store, now=1_000_000.0 + 5) is False
|
|
75
75
|
assert len(calls) == 1
|
|
76
76
|
argv, kwargs = calls[0]
|
|
77
|
-
|
|
77
|
+
# never cleaned up before, so the first run also cleans up
|
|
78
|
+
assert argv[1:] == [
|
|
79
|
+
"-m",
|
|
80
|
+
"witan_code",
|
|
81
|
+
"optimize",
|
|
82
|
+
"--store",
|
|
83
|
+
str(store),
|
|
84
|
+
"--cleanup-older-than",
|
|
85
|
+
maintenance.CLEANUP_OLDER_THAN,
|
|
86
|
+
]
|
|
78
87
|
assert kwargs["start_new_session"] is True
|
|
79
88
|
|
|
80
|
-
# after the window elapses it spawns again
|
|
81
|
-
|
|
89
|
+
# after the optimize window elapses it spawns again, without the cleanup,
|
|
90
|
+
# whose weekly window has not
|
|
91
|
+
assert maintenance.spawn_background_optimize(store, now=1_000_000.0 + 4000) is True
|
|
82
92
|
assert len(calls) == 2
|
|
93
|
+
assert "--cleanup-older-than" not in calls[1][0]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_cleanup_rides_optimize_on_its_own_window(monkeypatch, tmp_path):
|
|
97
|
+
"""omnigraph 0.11 reclaims deleted branches' storage only in `cleanup`, so
|
|
98
|
+
the throttled optimize has to run one now and then or local stores grow
|
|
99
|
+
without bound."""
|
|
100
|
+
from witan_code import maintenance
|
|
101
|
+
|
|
102
|
+
monkeypatch.setenv("TMPDIR", str(tmp_path))
|
|
103
|
+
monkeypatch.setenv("WITAN_CODE_OPTIMIZE_INTERVAL", "3600")
|
|
104
|
+
monkeypatch.setenv("WITAN_CODE_CLEANUP_INTERVAL", "86400")
|
|
105
|
+
store = tmp_path / "g.omni"
|
|
106
|
+
store.mkdir()
|
|
107
|
+
|
|
108
|
+
calls = []
|
|
109
|
+
monkeypatch.setattr(
|
|
110
|
+
maintenance.subprocess, "Popen", lambda argv, **kw: calls.append(argv)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
for hour in range(26):
|
|
114
|
+
maintenance.spawn_background_optimize(store, now=100_000.0 + hour * 3600)
|
|
115
|
+
cleaned = ["--cleanup-older-than" in argv for argv in calls]
|
|
116
|
+
assert len(calls) == 26
|
|
117
|
+
assert cleaned.count(True) == 2
|
|
118
|
+
assert cleaned[0] and cleaned[24]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_cleanup_disabled_leaves_optimize_alone(monkeypatch, tmp_path):
|
|
122
|
+
from witan_code import maintenance
|
|
123
|
+
|
|
124
|
+
monkeypatch.setenv("TMPDIR", str(tmp_path))
|
|
125
|
+
monkeypatch.setenv("WITAN_CODE_OPTIMIZE_INTERVAL", "3600")
|
|
126
|
+
monkeypatch.setenv("WITAN_CODE_CLEANUP_INTERVAL", "0")
|
|
127
|
+
store = tmp_path / "g.omni"
|
|
128
|
+
store.mkdir()
|
|
129
|
+
|
|
130
|
+
calls = []
|
|
131
|
+
monkeypatch.setattr(
|
|
132
|
+
maintenance.subprocess, "Popen", lambda argv, **kw: calls.append(argv)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
assert maintenance.spawn_background_optimize(store, now=50_000.0) is True
|
|
136
|
+
assert "--cleanup-older-than" not in calls[0]
|
|
83
137
|
|
|
84
138
|
|
|
85
139
|
def test_spawn_marks_before_spawn_so_failure_does_not_hotloop(monkeypatch, tmp_path):
|
|
@@ -177,6 +231,16 @@ def test_cli_optimize_and_cleanup(tmp_path, capsys):
|
|
|
177
231
|
assert "Cleaned up" in capsys.readouterr().out
|
|
178
232
|
|
|
179
233
|
|
|
234
|
+
@requires_omnigraph
|
|
235
|
+
def test_cli_optimize_then_cleanup(tmp_path, capsys):
|
|
236
|
+
from witan_code import cli as cli_module
|
|
237
|
+
|
|
238
|
+
store = _fresh_store(tmp_path)
|
|
239
|
+
|
|
240
|
+
cli_module.optimize(store=str(store), cleanup_older_than="30d")
|
|
241
|
+
assert "Optimized and cleaned up" in capsys.readouterr().out
|
|
242
|
+
|
|
243
|
+
|
|
180
244
|
def test_cli_optimize_missing_store_is_noop(tmp_path, capsys):
|
|
181
245
|
from witan_code import cli as cli_module
|
|
182
246
|
|
|
@@ -13,6 +13,8 @@ import json
|
|
|
13
13
|
import re
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
|
+
import pytest
|
|
17
|
+
|
|
16
18
|
from agent_config_kit import apply
|
|
17
19
|
from witan_code import setup
|
|
18
20
|
|
|
@@ -151,10 +153,18 @@ def _pi_setup_output(tmp_path, monkeypatch, capsys, *, dry_run: bool) -> str:
|
|
|
151
153
|
return " ".join(capsys.readouterr().out.split())
|
|
152
154
|
|
|
153
155
|
|
|
154
|
-
def
|
|
156
|
+
def _pi_settings(tmp_path, cfg: dict) -> None:
|
|
157
|
+
settings = tmp_path / ".pi" / "agent" / "settings.json"
|
|
158
|
+
settings.parent.mkdir(parents=True, exist_ok=True)
|
|
159
|
+
settings.write_text(json.dumps(cfg))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_setup_pi_warns_when_pi_cannot_load_mcp(tmp_path, monkeypatch, capsys):
|
|
155
163
|
"""`witan-code setup --agent pi` inherits agent-config-kit's MCP
|
|
156
|
-
prerequisite preflight: Pi ignores ~/.pi/agent/mcp.json
|
|
157
|
-
|
|
164
|
+
prerequisite preflight: Pi ignores ~/.pi/agent/mcp.json when its
|
|
165
|
+
built-in MCP is turned off and pi-mcp-adapter is not installed, so the
|
|
166
|
+
report must say so on dry-run and apply."""
|
|
167
|
+
_pi_settings(tmp_path, {"extensions": ["-builtin:mcp"]})
|
|
158
168
|
for dry_run in (True, False):
|
|
159
169
|
out = _pi_setup_output(tmp_path, monkeypatch, capsys, dry_run=dry_run)
|
|
160
170
|
assert "WARNING:" in out
|
|
@@ -162,9 +172,10 @@ def test_setup_pi_warns_when_pi_mcp_adapter_is_missing(tmp_path, monkeypatch, ca
|
|
|
162
172
|
|
|
163
173
|
|
|
164
174
|
def test_setup_pi_notes_when_pi_mcp_adapter_is_installed(tmp_path, monkeypatch, capsys):
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
175
|
+
_pi_settings(
|
|
176
|
+
tmp_path,
|
|
177
|
+
{"packages": ["npm:pi-mcp-adapter"], "extensions": ["-builtin:mcp"]},
|
|
178
|
+
)
|
|
168
179
|
|
|
169
180
|
out = _pi_setup_output(tmp_path, monkeypatch, capsys, dry_run=True)
|
|
170
181
|
|
|
@@ -173,6 +184,53 @@ def test_setup_pi_notes_when_pi_mcp_adapter_is_installed(tmp_path, monkeypatch,
|
|
|
173
184
|
assert "pi-mcp-adapter is declared in" in out
|
|
174
185
|
|
|
175
186
|
|
|
187
|
+
def test_setup_pi_notes_when_pi_builtin_mcp_is_on(tmp_path, monkeypatch, capsys):
|
|
188
|
+
out = _pi_setup_output(tmp_path, monkeypatch, capsys, dry_run=True)
|
|
189
|
+
|
|
190
|
+
assert "WARNING:" not in out
|
|
191
|
+
assert "note:" in out
|
|
192
|
+
assert "built-in MCP is not turned off" in out
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _dangling_pi_extension(tmp_path) -> Path:
|
|
196
|
+
dest = tmp_path / ".pi" / "agent" / "extensions" / "codegraph.ts"
|
|
197
|
+
dest.parent.mkdir(parents=True)
|
|
198
|
+
dest.symlink_to(tmp_path / "gone" / "codegraph.ts")
|
|
199
|
+
return dest
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_setup_exits_with_force_hint_on_a_symlinked_pi_extension(
|
|
203
|
+
tmp_path, monkeypatch, capsys
|
|
204
|
+
):
|
|
205
|
+
import witan_core
|
|
206
|
+
from witan_code import cli
|
|
207
|
+
|
|
208
|
+
monkeypatch.setattr(witan_core, "install_omnigraph", lambda dry_run, **kw: None)
|
|
209
|
+
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
210
|
+
dest = _dangling_pi_extension(tmp_path)
|
|
211
|
+
|
|
212
|
+
with pytest.raises(SystemExit) as exc:
|
|
213
|
+
cli.setup(agent="pi", author="tester")
|
|
214
|
+
|
|
215
|
+
assert exc.value.code == 2
|
|
216
|
+
assert "--force" in capsys.readouterr().out
|
|
217
|
+
assert dest.is_symlink()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def test_setup_force_replaces_a_symlinked_pi_extension(tmp_path, monkeypatch):
|
|
221
|
+
import witan_core
|
|
222
|
+
from witan_code import cli
|
|
223
|
+
|
|
224
|
+
monkeypatch.setattr(witan_core, "install_omnigraph", lambda dry_run, **kw: None)
|
|
225
|
+
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
226
|
+
dest = _dangling_pi_extension(tmp_path)
|
|
227
|
+
|
|
228
|
+
cli.setup(agent="pi", author="tester", force=True)
|
|
229
|
+
|
|
230
|
+
assert dest.is_file()
|
|
231
|
+
assert not dest.is_symlink()
|
|
232
|
+
|
|
233
|
+
|
|
176
234
|
# --- Pi extension source contract ------------------------------------------
|
|
177
235
|
#
|
|
178
236
|
# The Pi extension is TypeScript that no test here can execute, so these read
|
|
@@ -222,7 +280,16 @@ def test_configs_pi_mirror_matches_the_package_extension():
|
|
|
222
280
|
|
|
223
281
|
|
|
224
282
|
def test_pi_extension_asks_for_pi_rendered_context():
|
|
225
|
-
"""Without `--client
|
|
283
|
+
"""Without a Pi `--client` the block tells Pi to call a ToolSearch it
|
|
284
|
+
lacks, and with the wrong one it names the other MCP support's calling
|
|
285
|
+
convention: the extension must pick `pi` only when pi-mcp-adapter's
|
|
286
|
+
`mcp` tool is registered."""
|
|
226
287
|
source = _PKG_EXT.read_text()
|
|
227
288
|
|
|
228
|
-
assert
|
|
289
|
+
assert re.search(
|
|
290
|
+
r'spawnSync\(\s*"witan-code",\s*\["inject-context", "--client", client\]',
|
|
291
|
+
source,
|
|
292
|
+
)
|
|
293
|
+
assert "const client = piClient(pi);" in source
|
|
294
|
+
assert 'pi.getAllTools().some((tool) => tool?.name === "mcp")' in source
|
|
295
|
+
assert re.search(r'\?\s*"pi"\s*:\s*"pi-builtin"', source)
|