witan-code 0.22.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.
Files changed (110) hide show
  1. {witan_code-0.22.0 → witan_code-0.24.0}/CHANGELOG.md +77 -1
  2. {witan_code-0.22.0 → witan_code-0.24.0}/PKG-INFO +32 -21
  3. {witan_code-0.22.0 → witan_code-0.24.0}/README.md +29 -18
  4. {witan_code-0.22.0 → witan_code-0.24.0}/conftest.py +1 -1
  5. {witan_code-0.22.0 → witan_code-0.24.0}/docs/EDGE_PRECISION_TIERS.md +8 -6
  6. {witan_code-0.22.0 → witan_code-0.24.0}/docs/PACKAGE_MAP.md +1 -1
  7. {witan_code-0.22.0 → witan_code-0.24.0}/docs/STAGE2_STITCHING.md +9 -4
  8. {witan_code-0.22.0 → witan_code-0.24.0}/docs/SYMBOL_FORMAT.md +6 -6
  9. {witan_code-0.22.0 → witan_code-0.24.0}/docs/SYMBOL_TABLE.md +1 -1
  10. {witan_code-0.22.0 → witan_code-0.24.0}/pyproject.toml +15 -6
  11. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_branches.py +1 -2
  12. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_bridge.py +163 -59
  13. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_cli_hooks.py +6 -3
  14. witan_code-0.24.0/tests/test_cli_output_format.py +210 -0
  15. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_cli_selected_target.py +0 -3
  16. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_config_targets.py +0 -1
  17. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_context.py +22 -1
  18. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_edges.py +1 -2
  19. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_github_app.py +2 -1
  20. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_hooks.py +6 -4
  21. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_index_failure_diagnostics.py +22 -14
  22. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_indexer_scope.py +1 -1
  23. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_ingest.py +1 -2
  24. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_maintenance.py +69 -5
  25. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_remote_store.py +7 -3
  26. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_repo.py +0 -1
  27. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_setup.py +75 -11
  28. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_store.py +4 -4
  29. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_views.py +0 -1
  30. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_visualize.py +4 -1
  31. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/bridge.py +10 -6
  32. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/bridge_extractors.py +71 -34
  33. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/cli.py +148 -63
  34. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/config.py +1 -0
  35. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/context.py +48 -12
  36. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/extensions/pi/codegraph.ts +32 -9
  37. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/github_app.py +2 -2
  38. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/graph.py +2 -2
  39. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/hooks.py +1 -1
  40. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/identity.py +1 -1
  41. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/indexer.py +14 -12
  42. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/ingest.py +2 -2
  43. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/maintenance.py +49 -6
  44. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/remote/oidc.py +2 -1
  45. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/remote/proxy.py +1 -1
  46. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/remote/store.py +8 -7
  47. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/server.py +8 -10
  48. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/skills/witan-code/SKILL.md +15 -4
  49. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/store.py +7 -7
  50. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/visualize.py +6 -3
  51. witan_code-0.22.0/tests/test_cli_output_format.py +0 -95
  52. {witan_code-0.22.0 → witan_code-0.24.0}/.gitignore +0 -0
  53. {witan_code-0.22.0 → witan_code-0.24.0}/changelog.d/README.md +0 -0
  54. {witan_code-0.22.0 → witan_code-0.24.0}/config/claude.json +0 -0
  55. {witan_code-0.22.0 → witan_code-0.24.0}/config/copilot.json +0 -0
  56. {witan_code-0.22.0 → witan_code-0.24.0}/config/pi.json +0 -0
  57. {witan_code-0.22.0 → witan_code-0.24.0}/docs/BRANCH_INDEXING.md +0 -0
  58. {witan_code-0.22.0 → witan_code-0.24.0}/docs/CLI_REFERENCE.md +0 -0
  59. {witan_code-0.22.0 → witan_code-0.24.0}/docs/USER_GUIDE.md +0 -0
  60. {witan_code-0.22.0 → witan_code-0.24.0}/install.sh +0 -0
  61. {witan_code-0.22.0 → witan_code-0.24.0}/tests/__init__.py +0 -0
  62. {witan_code-0.22.0 → witan_code-0.24.0}/tests/conftest.py +0 -0
  63. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_batched_deletes.py +0 -0
  64. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_chunked_load.py +0 -0
  65. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_cli_dispatch.py +0 -0
  66. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_config.py +0 -0
  67. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_crossrepo.py +0 -0
  68. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_elicit.py +0 -0
  69. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_enrichment.py +0 -0
  70. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_graph.py +0 -0
  71. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_identity.py +0 -0
  72. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_index_reports_its_store.py +0 -0
  73. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_index_requires_a_repo_key.py +0 -0
  74. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_indexer.py +0 -0
  75. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_languages.py +0 -0
  76. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_package_map.py +0 -0
  77. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_reaper.py +0 -0
  78. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_rebuild_honours_the_repo_key.py +0 -0
  79. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_refusal_audit.py +0 -0
  80. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_remote_proxy.py +1 -1
  81. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_server.py +0 -0
  82. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_stitch.py +0 -0
  83. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_symbol_table.py +0 -0
  84. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_tasks.py +0 -0
  85. {witan_code-0.22.0 → witan_code-0.24.0}/tests/test_tool_schema.py +0 -0
  86. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/__init__.py +0 -0
  87. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/__main__.py +0 -0
  88. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/edges.py +0 -0
  89. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/elicit.py +0 -0
  90. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/output.py +0 -0
  91. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/package_map.py +0 -0
  92. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/queries/bridge.gq +0 -0
  93. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/queries/code_mutations.gq +0 -0
  94. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/queries/code_read.gq +0 -0
  95. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/queries/delete.gq +0 -0
  96. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/queries_ts/bash.scm +0 -0
  97. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/queries_ts/hcl.scm +0 -0
  98. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/queries_ts/python.scm +0 -0
  99. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/queries_ts/sql.scm +0 -0
  100. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/queries_ts/typescript.scm +0 -0
  101. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/queries_ts/yaml.scm +0 -0
  102. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/reaper.py +0 -0
  103. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/remote/__init__.py +0 -0
  104. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/repo.py +0 -0
  105. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/schema/bridge-schema.pg +0 -0
  106. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/schema/code-schema.pg +0 -0
  107. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/selected_target.py +0 -0
  108. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/setup.py +0 -0
  109. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/stitch.py +0 -0
  110. {witan_code-0.22.0 → witan_code-0.24.0}/witan_code/views.py +0 -0
@@ -8,6 +8,83 @@ 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
+
37
+ ## [0.23.0] - 2026-09-28
38
+
39
+ ### Added
40
+
41
+ - **Python package providers.** A `pyproject.toml` whose directory holds a
42
+ `mitol/<pkg>/__init__.py` namespace child now provides `mitol.<pkg>`, so
43
+ `mitol.*` imports join to ol-django. Previously only `@mitodl/*` npm
44
+ packages had a provider extractor and no Python import could form an edge.
45
+ An existing graph gains these providers on ol-django's next full-repo
46
+ `witan-code index` (the provider files are not source files, so the
47
+ unchanged-file skip does not apply to them); `witan-code reindex` on it
48
+ works too.
49
+
50
+ - **Repo-level provider files that stop producing bindings lose their old
51
+ rows.** A `pyproject.toml`, `package.json`, Pulumi config or OpenAPI spec
52
+ that emitted nothing on this run was never purged, so on a run that could
53
+ not purge by the collected file list (e.g. indexing outside a repository
54
+ root) its previous bindings stayed. Every such file scanned is now purged
55
+ before its fresh bindings are written.
56
+
57
+ ### Changed
58
+
59
+ - Allow cyclopts 5 (`cyclopts>=4,<6`). Under cyclopts 5, `--help` shows a
60
+ value placeholder after each option (e.g. `--target STR`), and a usage error
61
+ such as an unknown option exits with code 2 instead of 1.
62
+
63
+ ### Fixed
64
+
65
+ - **`witan code` listings emit `rows: []` on an empty result under
66
+ `--output-format`.** `symbols`, `stitch`, `stitch --unresolved` and `repos`
67
+ each returned early with a Rich sentence on stdout before the format was
68
+ consulted, so an empty result was not JSON/TOML/YAML. `_render_table` now
69
+ takes the empty message and prints it in `txt` mode only. `branches` never
70
+ consulted the format at all and now dumps `{repo, views, error}` rows, with
71
+ `--prune`'s progress lines on stderr under a structured format; the `repos`
72
+ unreadable-store warning and `doctor`'s summary and error lines move to
73
+ stderr there too.
74
+ - **Table titles and cells are escaped for Rich.** A symbol, path or title
75
+ containing `[...]` was read as markup and lost from the rendered table.
76
+
77
+ - **Endpoint bindings form cross-repo edges again.** Every endpoint consumer
78
+ scored 0.0 because the extractor only matches relative paths and the
79
+ `relative_url` heuristic subtracted 0.5 from the 0.5 baseline, so
80
+ `code_repo_dependencies` and the other heuristic-tier views filtered every
81
+ one out at their 0.5 cutoff. The `relative_url` penalty, its unreachable
82
+ `explicit_hostname` boost, and the `generated_file` penalty are removed. A
83
+ generated client for a repo's own API is still suppressed by
84
+ `self_provided_key`. Stored confidences are computed at index time, so
85
+ existing graphs need `witan-code reindex` (not `index`, which skips
86
+ unchanged files) on the consumer repos before these edges appear.
87
+
11
88
  ## [0.22.0] - 2026-09-25
12
89
 
13
90
  ### Added
@@ -302,7 +379,6 @@ a MINOR bump may include breaking changes).
302
379
  write threw". Deliberately still non-fatal — the per-repo index succeeds and
303
380
  is worth keeping — but "non-fatal" and "unreported" are different claims.
304
381
 
305
-
306
382
  - **The test suite no longer asserts an environment.** Four tests
307
383
  (`test_branches.py`'s three branch-view assertions and
308
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.22.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,8 +10,8 @@ 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.10
14
- Requires-Dist: cyclopts<5,>=4
13
+ Requires-Dist: agent-config-kit<1,>=0.12
14
+ Requires-Dist: cyclopts<6,>=4
15
15
  Requires-Dist: fastmcp<5,>=4
16
16
  Requires-Dist: joserfc<2,>=1.0
17
17
  Requires-Dist: pydantic<3,>=2
@@ -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
  ```
@@ -136,7 +136,7 @@ edges):
136
136
  |---|---|---|---|
137
137
  | `env_var` | Pulumi `<app>:env_vars` keys in `Pulumi.*.yaml` | `get_string("NAME",…)` / `os.environ` / `process.env.X` / `env("NAME")` | the `UPPER_SNAKE` name |
138
138
  | `endpoint` | drf-spectacular OpenAPI spec (`paths.<path>.<method>`) | `/api/…` path literals in TS (generated client / fetch) | normalized path (params → `{}`) |
139
- | `package` | a repo whose `package.json` name is `@mitodl/*` | `from mitol.*` / `include("mitol.*")` / `@mitodl/*` imports | the package name |
139
+ | `package` | a repo whose `package.json` name is `@mitodl/*`, or a `pyproject.toml` next to a `mitol/<pkg>/__init__.py` namespace child (provides `mitol.<pkg>`) | `from mitol.*` / `include("mitol.*")` / `@mitodl/*` imports | the package name |
140
140
  | `service` | `applications/<svc>/__main__.py` (repo / image / service name) | the deployed repo itself (key_norm = its canonical URI) | `repo:<uri>` / `image:…` / `name:…` |
141
141
 
142
142
  Generic env names (`DEBUG`, `PORT`, `SECRET_KEY`, …) are flagged `generic` and
@@ -146,7 +146,7 @@ every repo.
146
146
  The bridge is written in a **separate phase after the per-repo store write**, so
147
147
  the two stores' advisory write locks never nest (no deadlock) and a bridge
148
148
  failure never corrupts a per-repo store. A full-repo index runs the repo-level
149
- provider extractors (OpenAPI / Pulumi / service / `package.json`) and clears
149
+ provider extractors (OpenAPI / Pulumi / service / `package.json` / `pyproject.toml`) and clears
150
150
  bindings for files deleted from disk; all purging is per-file, so unchanged
151
151
  (skipped) files — and, for a narrow target like the reindex hook, sibling
152
152
  files — keep their bindings.
@@ -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 only by the
303
- third-party pi-mcp-adapter Pi package, since Pi core has no MCP support:
304
- install it with `pi install npm:pi-mcp-adapter`, restart Pi, and confirm it
305
- in `pi list`. `witan-code setup --agent pi` warns when it cannot find the
306
- package declared in Pi's settings.
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
- - **Scheduled**: `witan-code optimize [--store PATH | --bridge]` /
645
- `witan-code cleanup --yes` for cron/systemd-timer driven maintenance on a
646
- busy store. `optimize` is non-destructive and safe to run repeatedly;
647
- `cleanup` GCs old Lance versions to reclaim disk and is destructive, so it
648
- requires `--yes`.
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. `--client pi` (what the Pi
725
- extension passes) swaps the `ToolSearch` step for pi-mcp-adapter's `mcp`
726
- proxy: `mcp({ search: ... })` for the exact server-prefixed name, then
727
- `mcp({ tool: ..., args: {...} })`. The default stays Claude's.
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
  ```
@@ -103,7 +103,7 @@ edges):
103
103
  |---|---|---|---|
104
104
  | `env_var` | Pulumi `<app>:env_vars` keys in `Pulumi.*.yaml` | `get_string("NAME",…)` / `os.environ` / `process.env.X` / `env("NAME")` | the `UPPER_SNAKE` name |
105
105
  | `endpoint` | drf-spectacular OpenAPI spec (`paths.<path>.<method>`) | `/api/…` path literals in TS (generated client / fetch) | normalized path (params → `{}`) |
106
- | `package` | a repo whose `package.json` name is `@mitodl/*` | `from mitol.*` / `include("mitol.*")` / `@mitodl/*` imports | the package name |
106
+ | `package` | a repo whose `package.json` name is `@mitodl/*`, or a `pyproject.toml` next to a `mitol/<pkg>/__init__.py` namespace child (provides `mitol.<pkg>`) | `from mitol.*` / `include("mitol.*")` / `@mitodl/*` imports | the package name |
107
107
  | `service` | `applications/<svc>/__main__.py` (repo / image / service name) | the deployed repo itself (key_norm = its canonical URI) | `repo:<uri>` / `image:…` / `name:…` |
108
108
 
109
109
  Generic env names (`DEBUG`, `PORT`, `SECRET_KEY`, …) are flagged `generic` and
@@ -113,7 +113,7 @@ every repo.
113
113
  The bridge is written in a **separate phase after the per-repo store write**, so
114
114
  the two stores' advisory write locks never nest (no deadlock) and a bridge
115
115
  failure never corrupts a per-repo store. A full-repo index runs the repo-level
116
- provider extractors (OpenAPI / Pulumi / service / `package.json`) and clears
116
+ provider extractors (OpenAPI / Pulumi / service / `package.json` / `pyproject.toml`) and clears
117
117
  bindings for files deleted from disk; all purging is per-file, so unchanged
118
118
  (skipped) files — and, for a narrow target like the reindex hook, sibling
119
119
  files — keep their bindings.
@@ -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 only by the
270
- third-party pi-mcp-adapter Pi package, since Pi core has no MCP support:
271
- install it with `pi install npm:pi-mcp-adapter`, restart Pi, and confirm it
272
- in `pi list`. `witan-code setup --agent pi` warns when it cannot find the
273
- package declared in Pi's settings.
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
- - **Scheduled**: `witan-code optimize [--store PATH | --bridge]` /
612
- `witan-code cleanup --yes` for cron/systemd-timer driven maintenance on a
613
- busy store. `optimize` is non-destructive and safe to run repeatedly;
614
- `cleanup` GCs old Lance versions to reclaim disk and is destructive, so it
615
- requires `--yes`.
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. `--client pi` (what the Pi
692
- extension passes) swaps the `ToolSearch` step for pi-mcp-adapter's `mcp`
693
- proxy: `mcp({ search: ... })` for the exact server-prefixed name, then
694
- `mcp({ tool: ..., args: {...} })`. The default stays Claude's.
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.
@@ -23,4 +23,4 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
23
23
 
24
24
  # The import is the point: `testsupport.hermetic` redirects the environment at
25
25
  # module scope. The hook re-export is what lets this conftest report a leak.
26
- from testsupport.hermetic import pytest_sessionfinish # noqa: E402,F401
26
+ from testsupport.hermetic import pytest_sessionfinish # noqa: F401
@@ -35,12 +35,14 @@ up twice at two different trust levels.
35
35
 
36
36
  ```python
37
37
  TypedEdge(
38
- precision, # "precise" | "heuristic" | "fuzzy"
39
- consumer_repo, provider_repo,
40
- kind, key_norm,
41
- canonical_symbol, # consumer's symbol string; precise tier only, else None
42
- confidence, # 1.0 for precise; the heuristic tier's score otherwise
43
- evidence, # tuple[dict] of {repo, file, line} — see below
38
+ precision, # "precise" | "heuristic" | "fuzzy"
39
+ consumer_repo,
40
+ provider_repo,
41
+ kind,
42
+ key_norm,
43
+ canonical_symbol, # consumer's symbol string; precise tier only, else None
44
+ confidence, # 1.0 for precise; the heuristic tier's score otherwise
45
+ evidence, # tuple[dict] of {repo, file, line} — see below
44
46
  )
45
47
  ```
46
48
 
@@ -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
@@ -45,10 +45,15 @@ way a written edge could.
45
45
 
46
46
  ```python
47
47
  PreciseEdge(
48
- consumer_repo, consumer_symbol,
49
- provider_repo, provider_symbol,
50
- kind, scheme,
51
- match_count, preferred, ambiguous_version,
48
+ consumer_repo,
49
+ consumer_symbol,
50
+ provider_repo,
51
+ provider_symbol,
52
+ kind,
53
+ scheme,
54
+ match_count,
55
+ preferred,
56
+ ambiguous_version,
52
57
  )
53
58
  ```
54
59
 
@@ -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
 
@@ -93,7 +93,7 @@ bridge store predates `RepoSymbol` — the write is never blocked on it.
93
93
 
94
94
  ## Inspecting
95
95
 
96
- ```
96
+ ```text
97
97
  witan code symbols [--repo URI] [--role exported|external] [--scheme http]
98
98
  witan code stitch [--repo URI] [--unresolved]
99
99
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "witan-code"
3
- version = "0.22.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
- # lacks pi-mcp-adapter, which comes from `InstallResult.prerequisites`,
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
- "agent-config-kit>=0.10,<1",
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,13 +166,13 @@ 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 lacks pi-mcp-adapter, and
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.
164
173
  "witan-core[cli,remote,observability,sentry]>=0.40,<1",
165
174
  "fastmcp>=4,<5",
166
- "cyclopts>=4,<5",
175
+ "cyclopts>=4,<6",
167
176
  # [targets.<name>] override models (config.py) — also transitively pulled
168
177
  # in by fastmcp, declared explicitly since config.py imports it directly.
169
178
  "pydantic>=2,<3",
@@ -244,7 +253,7 @@ testpaths = ["tests"]
244
253
  packages = ["witan_code"]
245
254
 
246
255
  [tool.bumpversion]
247
- current_version = "0.22.0"
256
+ current_version = "0.24.0"
248
257
  allow_dirty = true
249
258
 
250
259
  [[tool.bumpversion.files]]
@@ -6,9 +6,8 @@ import subprocess
6
6
 
7
7
  import pytest
8
8
 
9
- from witan_core.omnigraph import OmnigraphConflict
10
-
11
9
  from witan_code import repo as repo_module
10
+ from witan_core.omnigraph import OmnigraphConflict
12
11
 
13
12
  from .conftest import SAMPLE, requires_stack
14
13