witan-code 0.14.0__tar.gz → 0.16.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.14.0 → witan_code-0.16.0}/.gitignore +3 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/CHANGELOG.md +165 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/PKG-INFO +55 -10
- {witan_code-0.14.0 → witan_code-0.16.0}/README.md +53 -8
- witan_code-0.16.0/conftest.py +26 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/docs/USER_GUIDE.md +6 -4
- {witan_code-0.14.0 → witan_code-0.16.0}/pyproject.toml +16 -11
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/conftest.py +44 -2
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_branches.py +45 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_cli_dispatch.py +60 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_context.py +131 -3
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_graph.py +43 -0
- witan_code-0.16.0/tests/test_indexer.py +142 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_setup.py +30 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_store.py +263 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/cli.py +211 -4
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/context.py +103 -6
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/graph.py +30 -5
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/indexer.py +25 -1
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/queries/bridge.gq +14 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/repo.py +22 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/server.py +52 -3
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/skills/witan-code/SKILL.md +9 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/store.py +179 -13
- witan_code-0.14.0/tests/test_indexer.py +0 -48
- {witan_code-0.14.0 → witan_code-0.16.0}/config/claude.json +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/config/copilot.json +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/config/pi.json +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/docs/BRANCH_INDEXING.md +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/docs/CLI_REFERENCE.md +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/docs/EDGE_PRECISION_TIERS.md +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/docs/PACKAGE_MAP.md +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/docs/STAGE2_STITCHING.md +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/docs/SYMBOL_FORMAT.md +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/docs/SYMBOL_TABLE.md +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/install.sh +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/__init__.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_batched_deletes.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_bridge.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_chunked_load.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_cli_hooks.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_cli_output_format.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_config.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_config_targets.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_crossrepo.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_edges.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_elicit.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_enrichment.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_github_app.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_hooks.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_identity.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_index_failure_diagnostics.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_indexer_scope.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_ingest.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_languages.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_maintenance.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_package_map.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_reaper.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_remote_proxy.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_remote_store.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_repo.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_server.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_stitch.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_symbol_table.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_tasks.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_tool_schema.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_views.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/tests/test_visualize.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/__init__.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/__main__.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/bridge.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/bridge_extractors.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/config.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/edges.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/elicit.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/extensions/pi/codegraph.ts +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/github_app.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/hooks.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/identity.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/ingest.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/maintenance.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/output.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/package_map.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/queries/code_mutations.gq +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/queries/code_read.gq +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/queries/delete.gq +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/queries_ts/bash.scm +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/queries_ts/hcl.scm +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/queries_ts/python.scm +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/queries_ts/sql.scm +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/queries_ts/typescript.scm +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/queries_ts/yaml.scm +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/reaper.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/remote/__init__.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/remote/oidc.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/remote/proxy.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/remote/store.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/schema/bridge-schema.pg +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/schema/code-schema.pg +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/setup.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/stitch.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/views.py +0 -0
- {witan_code-0.14.0 → witan_code-0.16.0}/witan_code/visualize.py +0 -0
|
@@ -6,6 +6,171 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/) (pre-1.0:
|
|
7
7
|
a MINOR bump may include breaking changes).
|
|
8
8
|
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **A failed cross-repo bridge write is reported instead of swallowed.** The
|
|
14
|
+
production bridge wedged for ~15 hours on 2026-08-25 and nothing said so:
|
|
15
|
+
every CI cycle logged the failure, printed `bindings=0 errors=0`, exited 0,
|
|
16
|
+
and raised no Sentry issue. Two independent causes.
|
|
17
|
+
|
|
18
|
+
The site logged at `warning`. `configure_sentry` installs
|
|
19
|
+
`LoggingIntegration(event_level=ERROR)` so a call site needs no
|
|
20
|
+
`capture_exception`, which makes the level the entire mechanism — and by that
|
|
21
|
+
contract a warning declares the failure "expected and already handled". A
|
|
22
|
+
throwing bridge write is neither. It logs at `error` now; verified end to end
|
|
23
|
+
against a recording transport (warning → 0 events, error → 1 issue with the
|
|
24
|
+
exception attached).
|
|
25
|
+
|
|
26
|
+
Sentry was also never initialised on that path: `configure_observability()`
|
|
27
|
+
ran only in `serve`, and the CI indexer runs `witan code index`. It now runs
|
|
28
|
+
in the CLI's meta launcher for every command, with `instrument=False` to keep
|
|
29
|
+
the OTel auto-instrumentors out of a short-lived CLI. Everything there no-ops
|
|
30
|
+
without its env var, so a developer with no `SENTRY_DSN` pays nothing.
|
|
31
|
+
|
|
32
|
+
The failure is now counted (`errors`) and flagged (`bridge=FAILED` in the
|
|
33
|
+
summary). `bindings=0` previously meant both "nothing to write" and "the
|
|
34
|
+
write threw". Deliberately still non-fatal — the per-repo index succeeds and
|
|
35
|
+
is worth keeping — but "non-fatal" and "unreported" are different claims.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
- **The test suite no longer asserts an environment.** Four tests
|
|
39
|
+
(`test_branches.py`'s three branch-view assertions and
|
|
40
|
+
`test_graph.py::test_a_shared_branch_view_needs_an_identity_to_own_it`)
|
|
41
|
+
failed on any machine whose `witan login` had resolved an actor, and passed
|
|
42
|
+
in CI, where a GitHub runner has none. They asserted un-namespaced view
|
|
43
|
+
names and the logged-out refusal prose — the shape identity resolution
|
|
44
|
+
happens to take when there is no identity — because nothing stopped
|
|
45
|
+
`identity.actor_id()` from reading the real `~/.config/witan/config.toml`
|
|
46
|
+
and OIDC token cache.
|
|
47
|
+
|
|
48
|
+
The `_fresh_identity` fixture now points `WITAN_CONFIG` and
|
|
49
|
+
`WITAN_TOKEN_CACHE` at `tmp_path` and clears the four env vars that
|
|
50
|
+
short-circuit ahead of them, so logged-out is deterministic everywhere. A
|
|
51
|
+
new `logged_in_actor` fixture opts back in through `WITAN_ACTOR`, and
|
|
52
|
+
`test_a_logged_in_writer_owns_the_views_it_indexes` uses it to cover the
|
|
53
|
+
half of `witan_code.views` a CI runner cannot reach: on a logged-in machine
|
|
54
|
+
the views carry their owner, on the local store too.
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
|
|
58
|
+
- **`graph.check_writable` requires `actor`; `None` now means "no identity",
|
|
59
|
+
full stop.** It used to fall back to `identity.actor_id()`, which gave the
|
|
60
|
+
parameter two meanings depending on the caller. Every call site
|
|
61
|
+
(`indexer`, `bridge`, `ingest`) already resolves an actor — it needs one to
|
|
62
|
+
*name* the view it is about to write — so nothing relied on the fallback,
|
|
63
|
+
and for `ingest` it was wrong: a request arriving with no actor was judged
|
|
64
|
+
against the serving process's identity rather than being refused for having
|
|
65
|
+
none.
|
|
66
|
+
|
|
67
|
+
## [0.16.0] - 2026-08-25
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
|
|
71
|
+
- **`witan-code doctor` and the `code_store_health` tool — a readiness check
|
|
72
|
+
that covers the bridge store.** Every per-repo graph *and* the shared
|
|
73
|
+
`_bridge.omni`, probed with the cheapest query that has to open a store to
|
|
74
|
+
answer. The bridge is the point: it belongs to no repo, so it appears in no
|
|
75
|
+
repo listing, and `code_interface_search` / `code_interface_providers` /
|
|
76
|
+
`code_interface_consumers` / `code_cross_repo_impact` all read it and
|
|
77
|
+
nothing else does. A bridge that cannot be opened breaks all of them while
|
|
78
|
+
`code_indexed_repos` still lists every repo happily.
|
|
79
|
+
|
|
80
|
+
That is not hypothetical. On the machine this was written on, all 54 local
|
|
81
|
+
stores had been unreadable since an omnigraph upgrade — the bridge for six
|
|
82
|
+
weeks — and nothing reported it: the failure surfaced only as an identical
|
|
83
|
+
Rust backtrace per tool call, and the prompt hook went on asserting
|
|
84
|
+
"N other repos indexed, so cross-repo `code_interface_*` resolve" on every
|
|
85
|
+
single prompt while none of them could run.
|
|
86
|
+
|
|
87
|
+
- **`witan-code reindex --rebuild`.** Deletes this repo's store, and the
|
|
88
|
+
bridge store if it is also unreadable, then indexes from scratch. Only the
|
|
89
|
+
stores that actually fail to open: `--rebuild` on a healthy store would
|
|
90
|
+
throw away a working index, and dropping a healthy bridge would take every
|
|
91
|
+
other repo's cross-repo bindings with it.
|
|
92
|
+
|
|
93
|
+
Deliberately not witan's `migrate storage` shape (export with the
|
|
94
|
+
pre-upgrade binary, reload with the new one, keep a `.pre-migrate` copy). A
|
|
95
|
+
memory graph holds the only copy of what it knows; a code graph holds a
|
|
96
|
+
derivation of a checkout that is still on disk. So this needs no old binary
|
|
97
|
+
and keeps no backup — a copy no installed binary can open is just disk, and
|
|
98
|
+
one of these stores is 27 GB.
|
|
99
|
+
|
|
100
|
+
Refuses a path that is not the repo root, since it deletes the whole store:
|
|
101
|
+
`reindex src/ --rebuild` would empty the store and refill only `src/`,
|
|
102
|
+
leaving the rest of the repo unindexed with nothing afterwards reporting it.
|
|
103
|
+
The root is resolved from the parent directory when the path is a FILE —
|
|
104
|
+
`index`/`reindex` accept one, and `git -C <file>` exits 128, which read as
|
|
105
|
+
"not a repo" and skipped the guard on the argument shape most likely to be
|
|
106
|
+
typed by accident.
|
|
107
|
+
|
|
108
|
+
A failed deletion raises instead of being swallowed. `ignore_errors=True`
|
|
109
|
+
left an unreadable store in place while the sidecars went anyway and the CLI
|
|
110
|
+
printed "Deleted … (N freed)", announcing a recovery that had not happened.
|
|
111
|
+
|
|
112
|
+
### Changed
|
|
113
|
+
|
|
114
|
+
- **A stale on-disk format now fails with a remedy instead of a Rust
|
|
115
|
+
backtrace.** witan-code's `OmnigraphClient` sets the `_STORAGE_MISMATCH_HINT`
|
|
116
|
+
that witan-core has always supported and witan has always set; without it
|
|
117
|
+
the client re-raised omnigraph's raw error — ANSI escapes, `Location:`,
|
|
118
|
+
backtrace boilerplate — once per tool call, advising an export path that
|
|
119
|
+
needs a binary the upgrade replaced.
|
|
120
|
+
|
|
121
|
+
- **`code_indexed_repos` distinguishes "unreadable" from "empty".** New
|
|
122
|
+
`unreadable` field carrying the reason; `files` stays `null`. The two used
|
|
123
|
+
to render identically, so a code graph that had been dead for weeks read as
|
|
124
|
+
a repo nobody had indexed much.
|
|
125
|
+
|
|
126
|
+
- **The `UserPromptSubmit` block no longer claims cross-repo resolution works
|
|
127
|
+
without checking.** It probes the bridge and says plainly when
|
|
128
|
+
`code_interface_*` will fail. A store the current repo cannot read replaces
|
|
129
|
+
the size/freshness line with a warning rather than rendering "? files" —
|
|
130
|
+
unless an index is in flight, which is the one benign way to see a store
|
|
131
|
+
that will not open yet.
|
|
132
|
+
|
|
133
|
+
The verdict is cached on disk, not in the process: `witan-code
|
|
134
|
+
inject-context` is a fresh process per prompt, so a module-level memo would
|
|
135
|
+
cache nothing and the block would cost a second store query on every prompt.
|
|
136
|
+
|
|
137
|
+
The bridge is probed with `bridge.gq/count_bindings`, not the per-repo
|
|
138
|
+
`code_read.gq/count_files` — the bridge schema has no `CodeFile` node, so
|
|
139
|
+
the per-repo probe condemns a perfectly healthy bridge. That is not a
|
|
140
|
+
cosmetic misreport, because `--rebuild` keys off the same verdict: it was
|
|
141
|
+
caught deleting each freshly-rebuilt bridge on the very next repo of a
|
|
142
|
+
rebuild sweep, taking every other repo's bindings with it each time.
|
|
143
|
+
|
|
144
|
+
A REMOTE bridge is always probed rather than gated on `StoreRef.exists`,
|
|
145
|
+
which degrades every failed remote probe to `False` by design. Gating on it
|
|
146
|
+
dropped an unreachable cluster bridge out of the report entirely and let
|
|
147
|
+
`code_store_health` answer `ok: true` while every `code_interface_*` tool
|
|
148
|
+
was failing — the exact condition the check exists to detect. A missing
|
|
149
|
+
LOCAL bridge is still absence, not failure: a repo with no contracts yet has
|
|
150
|
+
none, and reporting that as red would fail a healthy fresh install. The
|
|
151
|
+
prompt hook's own bridge probe carried the same defect and is fixed with it.
|
|
152
|
+
|
|
153
|
+
## [0.15.0] - 2026-08-21
|
|
154
|
+
|
|
155
|
+
### Changed
|
|
156
|
+
|
|
157
|
+
- **Raised the `witan-core` floor to `>=0.32` — a RUNTIME floor.** `setup` now
|
|
158
|
+
calls `install_omnigraph(dry_run, strict=False)` to keep the old
|
|
159
|
+
keep-going-on-a-refusal behaviour, which witan-core 0.32.0 made opt-in when
|
|
160
|
+
it started raising `OmnigraphInstallFailed` by default (see witan-core's
|
|
161
|
+
CHANGELOG for why the CI callers needed that default). Anything below 0.32
|
|
162
|
+
has no `strict` parameter, so resolving below it is a `TypeError` **when
|
|
163
|
+
setup runs** — the module imports fine and every test passes, which is
|
|
164
|
+
exactly the case `just check-core-floor` documents itself as unable to catch.
|
|
165
|
+
|
|
166
|
+
No behaviour change for anyone: `setup` keeps going on a refused binary and
|
|
167
|
+
keeps printing the reason, as before.
|
|
168
|
+
|
|
169
|
+
0.32, not the 0.31 this branch was authored against: agent-kit#276 took
|
|
170
|
+
0.31.0 while this was open. This floor has now moved twice on rebase and the
|
|
171
|
+
pyproject line auto-merged silently both times, since both sides named the
|
|
172
|
+
same number — re-check it by hand on every rebase.
|
|
173
|
+
|
|
9
174
|
## [0.14.0] - 2026-08-21
|
|
10
175
|
|
|
11
176
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: witan-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.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
|
|
@@ -26,7 +26,7 @@ Requires-Dist: tree-sitter-sql<0.4,>=0.3
|
|
|
26
26
|
Requires-Dist: tree-sitter-typescript<0.24,>=0.23
|
|
27
27
|
Requires-Dist: tree-sitter-yaml<0.8,>=0.7
|
|
28
28
|
Requires-Dist: tree-sitter<0.26,>=0.25
|
|
29
|
-
Requires-Dist: witan-core[cli,observability,remote,sentry]<1,>=0.
|
|
29
|
+
Requires-Dist: witan-core[cli,observability,remote,sentry]<1,>=0.32
|
|
30
30
|
Provides-Extra: tasks
|
|
31
31
|
Requires-Dist: fastmcp-tasks<5,>=4.0.0b1; extra == 'tasks'
|
|
32
32
|
Description-Content-Type: text/markdown
|
|
@@ -41,13 +41,13 @@ repository's symbols (functions, methods, classes, modules) and their
|
|
|
41
41
|
relationships, then exposes definition / reference / caller / impact queries to
|
|
42
42
|
the agent.
|
|
43
43
|
|
|
44
|
-
It is a
|
|
45
|
-
subprocess/CLI conventions, but stores a
|
|
46
|
-
graph.
|
|
44
|
+
It is a sibling of `witan` (Layer 1) — it never imports it, though both build on
|
|
45
|
+
`witan-core` — and shares its subprocess/CLI conventions, but stores a
|
|
46
|
+
**separate, per-repo** graph rather than writing into Layer 1's.
|
|
47
47
|
|
|
48
48
|
> **Wiring it into your agents locally** (MCP server, the `PostToolUse` reindex
|
|
49
49
|
> hook, and the indexer CLI, run straight from your checkout): see
|
|
50
|
-
> [Local Development Setup](../../../docs/agent-memory.md#local-development-setup).
|
|
50
|
+
> [Local Development Setup](../../../docs/internals/agent-memory.md#local-development-setup).
|
|
51
51
|
|
|
52
52
|
## When to use this vs. grep / the `Explore` agent
|
|
53
53
|
|
|
@@ -70,7 +70,14 @@ resolution, not a true call graph) — see
|
|
|
70
70
|
| Layer | Server | Stores | Scope | Synced |
|
|
71
71
|
|------|--------|--------|-------|--------|
|
|
72
72
|
| 1 | `witan` | patterns, project facts, lessons, workflow traces | team-wide | yes (S3) |
|
|
73
|
-
| 2 | `witan-code` | code symbols + edges | per-repo |
|
|
73
|
+
| 2 | `witan-code` | code symbols + edges | per-repo | local by default; shareable |
|
|
74
|
+
|
|
75
|
+
Layer 2 was local-only originally and is no longer. A code graph can live on a
|
|
76
|
+
shared `omnigraph-server` (`WITAN_CODE_SERVER` in-cluster, or through the
|
|
77
|
+
deployed witan MCP tier from outside it), where a CI indexer owns each repo's
|
|
78
|
+
default `main` view and every other writer gets its own per-actor branch view.
|
|
79
|
+
On a local store there is one user, who is its writer, and none of that
|
|
80
|
+
arbitration applies. See [`docs/BRANCH_INDEXING.md`](docs/BRANCH_INDEXING.md).
|
|
74
81
|
|
|
75
82
|
The two layers compose through **soft symbol-ID references**. A Layer-1 node
|
|
76
83
|
(e.g. a `lesson` or `agent_context`) can record symbol ids of the form:
|
|
@@ -115,7 +122,7 @@ The per-repo graph stops at a repo boundary, but service-oriented architectures
|
|
|
115
122
|
couple repos through **shared contracts**: an env var that infra sets and an app
|
|
116
123
|
reads, an HTTP endpoint one service serves and another calls, a package one repo
|
|
117
124
|
publishes and others import. The bridge records these as **interface bindings**
|
|
118
|
-
in a single shared
|
|
125
|
+
in a single shared store (`_bridge.omni` locally, a sibling of the per-repo
|
|
119
126
|
stores) so linkages can be queried across every indexed repo.
|
|
120
127
|
|
|
121
128
|
It is **zero-config**: every `index`/reindex of a repo also extracts that repo's
|
|
@@ -519,8 +526,9 @@ graph):
|
|
|
519
526
|
|
|
520
527
|
| Tool | Returns |
|
|
521
528
|
|------|---------|
|
|
522
|
-
| `code_indexed_repos()` | every indexed repo with file count, size, and last-indexed timestamp |
|
|
529
|
+
| `code_indexed_repos()` | every indexed repo with file count, size, and last-indexed timestamp; `files: null` + `unreadable: <why>` for a store no `code_*` tool can query |
|
|
523
530
|
| `code_indexed_branches(branch=None)` | the in-flight branch views each repo's store carries, and who owns each; pass a git branch to see every writer's view of it |
|
|
531
|
+
| `code_store_health()` | whether every code graph opens — per-repo **and the bridge**; the readiness check to run before reading an empty result as "nothing found" (see [Rebuilding an unreadable store](#rebuilding-an-unreadable-store)) |
|
|
524
532
|
|
|
525
533
|
## CLI
|
|
526
534
|
|
|
@@ -529,7 +537,12 @@ graph):
|
|
|
529
537
|
- `setup [--dry-run]` — fetch the pinned omnigraph binary to `~/.local/bin/`
|
|
530
538
|
for standalone use (see [Install](#install)).
|
|
531
539
|
- `index [PATH]` — incremental; skips files whose content hash is unchanged.
|
|
532
|
-
- `reindex [PATH]` — force rebuild a path.
|
|
540
|
+
- `reindex [PATH] [--rebuild] [--yes]` — force rebuild a path. `--rebuild`
|
|
541
|
+
first deletes this repo's store, and the bridge store, *if they cannot be
|
|
542
|
+
read* — see [Rebuilding an unreadable store](#rebuilding-an-unreadable-store).
|
|
543
|
+
- `doctor` — check that every code graph opens: each per-repo store **and the
|
|
544
|
+
shared bridge store**, which belongs to no repo and so appears in no other
|
|
545
|
+
listing. Exits non-zero if any is unreadable, so it works as a check.
|
|
533
546
|
- `repos` — list all indexed repos with file count, symbol count, and store size.
|
|
534
547
|
- `branches [--branch B] [--prune]` — list the in-flight branch views per
|
|
535
548
|
store; `--branch` shows every writer's view of one git branch (how you find
|
|
@@ -628,6 +641,38 @@ mechanisms keep this in check, mirroring that module (deliberately duplicated
|
|
|
628
641
|
`cleanup` GCs old Lance versions to reclaim disk and is destructive, so it
|
|
629
642
|
requires `--yes`.
|
|
630
643
|
|
|
644
|
+
## Rebuilding an unreadable store
|
|
645
|
+
|
|
646
|
+
omnigraph uses strict single-version storage: a release that bumps the
|
|
647
|
+
on-disk format refuses to open a graph an older binary wrote (`__manifest is
|
|
648
|
+
stamped at internal schema vN, but this omnigraph reads only vM`). Every
|
|
649
|
+
`code_*` read against such a store errors, permanently, until it is rebuilt.
|
|
650
|
+
|
|
651
|
+
`witan-code doctor` is how you find out. It probes every per-repo store **and
|
|
652
|
+
the bridge store**, which is the one that goes unwatched: it belongs to no
|
|
653
|
+
repo, so it is in no repo listing, and `code_interface_search` /
|
|
654
|
+
`code_interface_providers` / `code_interface_consumers` /
|
|
655
|
+
`code_cross_repo_impact` all read it and nothing else does. A bridge that
|
|
656
|
+
cannot be opened breaks all of those while every per-repo listing still looks
|
|
657
|
+
healthy.
|
|
658
|
+
|
|
659
|
+
The fix is **not** witan's `witan migrate storage` (export with the
|
|
660
|
+
pre-upgrade binary, reload with the new one). A code graph is *derived* from a
|
|
661
|
+
checkout that is still on disk, so reindexing rebuilds it from source and
|
|
662
|
+
needs no old binary:
|
|
663
|
+
|
|
664
|
+
```console
|
|
665
|
+
witan-code doctor # which stores, and why
|
|
666
|
+
witan-code reindex --rebuild # from each affected checkout
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
`--rebuild` deletes only the stores it could not read — this repo's, and the
|
|
670
|
+
bridge if it is unreadable too — then indexes from scratch. Nothing is kept
|
|
671
|
+
aside: a copy no installed binary can open is just disk. Dropping the bridge
|
|
672
|
+
does cost every *other* repo's cross-repo bindings, and each restores its own
|
|
673
|
+
on its next index, so a machine with many indexed repos wants a
|
|
674
|
+
`witan-code reindex --rebuild` pass over each checkout it cares about.
|
|
675
|
+
|
|
631
676
|
## Skill
|
|
632
677
|
|
|
633
678
|
[`witan_code/skills/witan-code/SKILL.md`](witan_code/skills/witan-code/SKILL.md)
|
|
@@ -8,13 +8,13 @@ repository's symbols (functions, methods, classes, modules) and their
|
|
|
8
8
|
relationships, then exposes definition / reference / caller / impact queries to
|
|
9
9
|
the agent.
|
|
10
10
|
|
|
11
|
-
It is a
|
|
12
|
-
subprocess/CLI conventions, but stores a
|
|
13
|
-
graph.
|
|
11
|
+
It is a sibling of `witan` (Layer 1) — it never imports it, though both build on
|
|
12
|
+
`witan-core` — and shares its subprocess/CLI conventions, but stores a
|
|
13
|
+
**separate, per-repo** graph rather than writing into Layer 1's.
|
|
14
14
|
|
|
15
15
|
> **Wiring it into your agents locally** (MCP server, the `PostToolUse` reindex
|
|
16
16
|
> hook, and the indexer CLI, run straight from your checkout): see
|
|
17
|
-
> [Local Development Setup](../../../docs/agent-memory.md#local-development-setup).
|
|
17
|
+
> [Local Development Setup](../../../docs/internals/agent-memory.md#local-development-setup).
|
|
18
18
|
|
|
19
19
|
## When to use this vs. grep / the `Explore` agent
|
|
20
20
|
|
|
@@ -37,7 +37,14 @@ resolution, not a true call graph) — see
|
|
|
37
37
|
| Layer | Server | Stores | Scope | Synced |
|
|
38
38
|
|------|--------|--------|-------|--------|
|
|
39
39
|
| 1 | `witan` | patterns, project facts, lessons, workflow traces | team-wide | yes (S3) |
|
|
40
|
-
| 2 | `witan-code` | code symbols + edges | per-repo |
|
|
40
|
+
| 2 | `witan-code` | code symbols + edges | per-repo | local by default; shareable |
|
|
41
|
+
|
|
42
|
+
Layer 2 was local-only originally and is no longer. A code graph can live on a
|
|
43
|
+
shared `omnigraph-server` (`WITAN_CODE_SERVER` in-cluster, or through the
|
|
44
|
+
deployed witan MCP tier from outside it), where a CI indexer owns each repo's
|
|
45
|
+
default `main` view and every other writer gets its own per-actor branch view.
|
|
46
|
+
On a local store there is one user, who is its writer, and none of that
|
|
47
|
+
arbitration applies. See [`docs/BRANCH_INDEXING.md`](docs/BRANCH_INDEXING.md).
|
|
41
48
|
|
|
42
49
|
The two layers compose through **soft symbol-ID references**. A Layer-1 node
|
|
43
50
|
(e.g. a `lesson` or `agent_context`) can record symbol ids of the form:
|
|
@@ -82,7 +89,7 @@ The per-repo graph stops at a repo boundary, but service-oriented architectures
|
|
|
82
89
|
couple repos through **shared contracts**: an env var that infra sets and an app
|
|
83
90
|
reads, an HTTP endpoint one service serves and another calls, a package one repo
|
|
84
91
|
publishes and others import. The bridge records these as **interface bindings**
|
|
85
|
-
in a single shared
|
|
92
|
+
in a single shared store (`_bridge.omni` locally, a sibling of the per-repo
|
|
86
93
|
stores) so linkages can be queried across every indexed repo.
|
|
87
94
|
|
|
88
95
|
It is **zero-config**: every `index`/reindex of a repo also extracts that repo's
|
|
@@ -486,8 +493,9 @@ graph):
|
|
|
486
493
|
|
|
487
494
|
| Tool | Returns |
|
|
488
495
|
|------|---------|
|
|
489
|
-
| `code_indexed_repos()` | every indexed repo with file count, size, and last-indexed timestamp |
|
|
496
|
+
| `code_indexed_repos()` | every indexed repo with file count, size, and last-indexed timestamp; `files: null` + `unreadable: <why>` for a store no `code_*` tool can query |
|
|
490
497
|
| `code_indexed_branches(branch=None)` | the in-flight branch views each repo's store carries, and who owns each; pass a git branch to see every writer's view of it |
|
|
498
|
+
| `code_store_health()` | whether every code graph opens — per-repo **and the bridge**; the readiness check to run before reading an empty result as "nothing found" (see [Rebuilding an unreadable store](#rebuilding-an-unreadable-store)) |
|
|
491
499
|
|
|
492
500
|
## CLI
|
|
493
501
|
|
|
@@ -496,7 +504,12 @@ graph):
|
|
|
496
504
|
- `setup [--dry-run]` — fetch the pinned omnigraph binary to `~/.local/bin/`
|
|
497
505
|
for standalone use (see [Install](#install)).
|
|
498
506
|
- `index [PATH]` — incremental; skips files whose content hash is unchanged.
|
|
499
|
-
- `reindex [PATH]` — force rebuild a path.
|
|
507
|
+
- `reindex [PATH] [--rebuild] [--yes]` — force rebuild a path. `--rebuild`
|
|
508
|
+
first deletes this repo's store, and the bridge store, *if they cannot be
|
|
509
|
+
read* — see [Rebuilding an unreadable store](#rebuilding-an-unreadable-store).
|
|
510
|
+
- `doctor` — check that every code graph opens: each per-repo store **and the
|
|
511
|
+
shared bridge store**, which belongs to no repo and so appears in no other
|
|
512
|
+
listing. Exits non-zero if any is unreadable, so it works as a check.
|
|
500
513
|
- `repos` — list all indexed repos with file count, symbol count, and store size.
|
|
501
514
|
- `branches [--branch B] [--prune]` — list the in-flight branch views per
|
|
502
515
|
store; `--branch` shows every writer's view of one git branch (how you find
|
|
@@ -595,6 +608,38 @@ mechanisms keep this in check, mirroring that module (deliberately duplicated
|
|
|
595
608
|
`cleanup` GCs old Lance versions to reclaim disk and is destructive, so it
|
|
596
609
|
requires `--yes`.
|
|
597
610
|
|
|
611
|
+
## Rebuilding an unreadable store
|
|
612
|
+
|
|
613
|
+
omnigraph uses strict single-version storage: a release that bumps the
|
|
614
|
+
on-disk format refuses to open a graph an older binary wrote (`__manifest is
|
|
615
|
+
stamped at internal schema vN, but this omnigraph reads only vM`). Every
|
|
616
|
+
`code_*` read against such a store errors, permanently, until it is rebuilt.
|
|
617
|
+
|
|
618
|
+
`witan-code doctor` is how you find out. It probes every per-repo store **and
|
|
619
|
+
the bridge store**, which is the one that goes unwatched: it belongs to no
|
|
620
|
+
repo, so it is in no repo listing, and `code_interface_search` /
|
|
621
|
+
`code_interface_providers` / `code_interface_consumers` /
|
|
622
|
+
`code_cross_repo_impact` all read it and nothing else does. A bridge that
|
|
623
|
+
cannot be opened breaks all of those while every per-repo listing still looks
|
|
624
|
+
healthy.
|
|
625
|
+
|
|
626
|
+
The fix is **not** witan's `witan migrate storage` (export with the
|
|
627
|
+
pre-upgrade binary, reload with the new one). A code graph is *derived* from a
|
|
628
|
+
checkout that is still on disk, so reindexing rebuilds it from source and
|
|
629
|
+
needs no old binary:
|
|
630
|
+
|
|
631
|
+
```console
|
|
632
|
+
witan-code doctor # which stores, and why
|
|
633
|
+
witan-code reindex --rebuild # from each affected checkout
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
`--rebuild` deletes only the stores it could not read — this repo's, and the
|
|
637
|
+
bridge if it is unreadable too — then indexes from scratch. Nothing is kept
|
|
638
|
+
aside: a copy no installed binary can open is just disk. Dropping the bridge
|
|
639
|
+
does cost every *other* repo's cross-repo bindings, and each restores its own
|
|
640
|
+
on its next index, so a machine with many indexed repos wants a
|
|
641
|
+
`witan-code reindex --rebuild` pass over each checkout it cares about.
|
|
642
|
+
|
|
598
643
|
## Skill
|
|
599
644
|
|
|
600
645
|
[`witan_code/skills/witan-code/SKILL.md`](witan_code/skills/witan-code/SKILL.md)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Load the workspace's shared test-environment guard.
|
|
2
|
+
|
|
3
|
+
This file exists to be imported EARLY. ``testsupport.hermetic`` redirects every
|
|
4
|
+
ambient input — HOME, the XDG dirs, the witan state files and graph stores, the
|
|
5
|
+
terminal width — at import time rather than in a fixture, because the leak it
|
|
6
|
+
exists to stop can happen while pytest is still collecting (importing
|
|
7
|
+
``witan.server`` creates a graph). A rootdir ``conftest.py`` is the earliest
|
|
8
|
+
hook that runs for every invocation.
|
|
9
|
+
|
|
10
|
+
★ IMPORTED, not named in ``pytest_plugins``. That setting is only honoured in
|
|
11
|
+
whichever conftest is TOP-LEVEL for the rootdir pytest picked, and the rootdir
|
|
12
|
+
depends on the arguments: run ``pytest`` from the repo root and all five of
|
|
13
|
+
these become non-top-level, aborting collection outright with "Defining
|
|
14
|
+
'pytest_plugins' in a non-top-level conftest is no longer supported". A plain
|
|
15
|
+
import carries no such rule, does the same redirection (it happens at module
|
|
16
|
+
import), and re-exporting the hook below makes this conftest its own plugin.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
|
|
23
|
+
|
|
24
|
+
# The import is the point: `testsupport.hermetic` redirects the environment at
|
|
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
|
|
@@ -171,10 +171,12 @@ not indexed yet (HCL/Terraform is the leading candidate).
|
|
|
171
171
|
## Cross-repo bridge in brief
|
|
172
172
|
|
|
173
173
|
Per-repo indexing stops at the repo boundary; the bridge is a separate,
|
|
174
|
-
shared
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
174
|
+
shared store that links repos through contracts they share — an env var one
|
|
175
|
+
repo's infra sets and another reads, an HTTP endpoint one serves and another
|
|
176
|
+
calls, a package one publishes and others import, a service one repo deploys.
|
|
177
|
+
It follows the same store resolution as the per-repo graphs: `_bridge.omni`
|
|
178
|
+
beside them locally, and the deployment's bridge graph when one is configured
|
|
179
|
+
(`witan_code.store.bridge_store`). It is **zero-config**: every `index` /
|
|
178
180
|
`reindex` of any repo also extracts that repo's bindings into the bridge
|
|
179
181
|
store automatically — there's no registry of repos to maintain.
|
|
180
182
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "witan-code"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.16.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"
|
|
@@ -108,15 +108,20 @@ dependencies = [
|
|
|
108
108
|
# self-describing failure and delivers half of one is the quiet wrongness
|
|
109
109
|
# the two floors above were raised to avoid.
|
|
110
110
|
#
|
|
111
|
-
# ★
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
|
|
111
|
+
# ★ >=0.32 IS THE FOURTH NON-IMPORT FLOOR, and the first RUNTIME one — a
|
|
112
|
+
# worked example of what `just check-core-floor` cannot see. `cli.setup`
|
|
113
|
+
# calls `install_omnigraph(dry_run, strict=False)`; anything below 0.32 has
|
|
114
|
+
# no `strict` parameter, so that is a TypeError raised WHEN SETUP RUNS — the
|
|
115
|
+
# module imports fine, every test passes, and the clean-install import check
|
|
116
|
+
# is green. Nothing catches this but the pin, which is why the pin says so.
|
|
117
|
+
# It subsumes the diagnostics reason above, which needed only >=0.31.
|
|
118
|
+
#
|
|
119
|
+
# ★ THIS NUMBER HAS MOVED TWICE ON REBASE — 0.30 -> 0.31 -> 0.32 — because
|
|
120
|
+
# agent-kit#280 then #276 each took the next witan-core release while these
|
|
121
|
+
# branches were open. Both times the pyproject line auto-merged without a
|
|
122
|
+
# conflict, since both sides named the same number. Re-check it by hand on
|
|
123
|
+
# every rebase; a floor one release low here is silent.
|
|
124
|
+
"witan-core[cli,remote,observability,sentry]>=0.32,<1",
|
|
120
125
|
"fastmcp>=3.4.2,<5",
|
|
121
126
|
"cyclopts>=4,<5",
|
|
122
127
|
# [targets.<name>] override models (config.py) — also transitively pulled
|
|
@@ -200,7 +205,7 @@ testpaths = ["tests"]
|
|
|
200
205
|
packages = ["witan_code"]
|
|
201
206
|
|
|
202
207
|
[tool.bumpversion]
|
|
203
|
-
current_version = "0.
|
|
208
|
+
current_version = "0.16.0"
|
|
204
209
|
allow_dirty = true
|
|
205
210
|
|
|
206
211
|
[[tool.bumpversion.files]]
|
|
@@ -41,21 +41,63 @@ def main():
|
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
# Every input `identity._resolve` reads. Isolating the file paths is not
|
|
45
|
+
# enough on its own: each of these env vars short-circuits ahead of them.
|
|
46
|
+
_IDENTITY_ENV = (
|
|
47
|
+
"WITAN_ACTOR",
|
|
48
|
+
"WITAN_REMOTE_URL",
|
|
49
|
+
"WITAN_OIDC_ISSUER",
|
|
50
|
+
"WITAN_TARGET",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
44
54
|
@pytest.fixture(autouse=True)
|
|
45
|
-
def _fresh_identity():
|
|
46
|
-
"""
|
|
55
|
+
def _fresh_identity(tmp_path, monkeypatch):
|
|
56
|
+
"""Pin the identity these tests run under: nobody, unless one is asked for.
|
|
47
57
|
|
|
58
|
+
Two jobs. The first is to forget the process-lifetime actor between tests —
|
|
48
59
|
``identity.actor_id`` memoizes deliberately (a witan-code process writes as
|
|
49
60
|
exactly one identity), which without this would let the first test that
|
|
50
61
|
resolves one decide the branch-view names for every test after it.
|
|
62
|
+
|
|
63
|
+
The second is to stop that resolution reaching the machine. It reads the
|
|
64
|
+
real ``~/.config/witan/config.toml`` and the real OIDC token cache, so on a
|
|
65
|
+
developer's box — logged in, unlike a CI runner — ``actor_id()`` returns an
|
|
66
|
+
``act-…`` and the branch views get namespaced under it. Four tests asserted
|
|
67
|
+
un-namespaced names and failed for everyone who had run ``witan login``,
|
|
68
|
+
green in CI the whole time. Pointing both at ``tmp_path`` makes logged-out
|
|
69
|
+
the deterministic default everywhere; ``logged_in_actor`` opts back in.
|
|
51
70
|
"""
|
|
52
71
|
from witan_code import identity
|
|
53
72
|
|
|
73
|
+
monkeypatch.setenv("WITAN_CONFIG", str(tmp_path / "witan-config.toml"))
|
|
74
|
+
monkeypatch.setenv("WITAN_TOKEN_CACHE", str(tmp_path / "witan-tokens.json"))
|
|
75
|
+
for var in _IDENTITY_ENV:
|
|
76
|
+
monkeypatch.delenv(var, raising=False)
|
|
77
|
+
|
|
54
78
|
identity.reset_cache()
|
|
55
79
|
yield
|
|
56
80
|
identity.reset_cache()
|
|
57
81
|
|
|
58
82
|
|
|
83
|
+
@pytest.fixture
|
|
84
|
+
def logged_in_actor(monkeypatch):
|
|
85
|
+
"""Run the test as a specific actor — the half of the guard CI never sees.
|
|
86
|
+
|
|
87
|
+
Set through ``WITAN_ACTOR`` rather than by stubbing ``actor_id``, so the
|
|
88
|
+
resolution path a non-interactive writer (the CI indexer, a maintenance
|
|
89
|
+
job) actually takes is the one under test.
|
|
90
|
+
"""
|
|
91
|
+
from witan_code import identity
|
|
92
|
+
|
|
93
|
+
def _login(actor: str = "act-alice") -> str:
|
|
94
|
+
monkeypatch.setenv(identity.ACTOR_ENV_VAR, actor)
|
|
95
|
+
identity.reset_cache()
|
|
96
|
+
return actor
|
|
97
|
+
|
|
98
|
+
return _login
|
|
99
|
+
|
|
100
|
+
|
|
59
101
|
@pytest.fixture(autouse=True)
|
|
60
102
|
def _fresh_git_context():
|
|
61
103
|
"""Forget the memoized git context between tests.
|
|
@@ -155,6 +155,51 @@ def test_feature_branch_indexes_to_own_branch(tmp_path, monkeypatch):
|
|
|
155
155
|
assert not on_main, "main view must not see in-flight branch symbols"
|
|
156
156
|
|
|
157
157
|
|
|
158
|
+
@requires_stack
|
|
159
|
+
def test_a_logged_in_writer_owns_the_views_it_indexes(
|
|
160
|
+
tmp_path, monkeypatch, logged_in_actor
|
|
161
|
+
):
|
|
162
|
+
"""The other half of `witan_code.views`, which CI never reaches.
|
|
163
|
+
|
|
164
|
+
A GitHub runner has no witan identity, so every branch-view assertion above
|
|
165
|
+
is of the un-namespaced name. On a logged-in machine — every real user —
|
|
166
|
+
the views carry their owner, on the local store too (there is no second
|
|
167
|
+
naming rule for local stores). Asserting it here is what keeps the two
|
|
168
|
+
halves from drifting.
|
|
169
|
+
"""
|
|
170
|
+
from witan_code import config as cfg_module
|
|
171
|
+
from witan_code import indexer
|
|
172
|
+
from witan_code.graph import OmnigraphClient
|
|
173
|
+
from witan_code.views import owner
|
|
174
|
+
|
|
175
|
+
actor = logged_in_actor("act-alice")
|
|
176
|
+
monkeypatch.setenv("WITAN_REPO", REPO)
|
|
177
|
+
monkeypatch.setenv("WITAN_CODE_DIR", str(tmp_path / "code"))
|
|
178
|
+
cfg = cfg_module.load()
|
|
179
|
+
|
|
180
|
+
base = _git_repo(tmp_path / "r")
|
|
181
|
+
(base / "svc.py").write_text(SAMPLE)
|
|
182
|
+
indexer.index_path(base, config=cfg)
|
|
183
|
+
|
|
184
|
+
_git(base, "checkout", "-q", "-b", "feature/new-api")
|
|
185
|
+
(base / "extra.py").write_text("def branch_only_symbol():\n return 2\n")
|
|
186
|
+
indexer.index_path(base, config=cfg)
|
|
187
|
+
|
|
188
|
+
view = f"{actor}/feature_new-api"
|
|
189
|
+
store = str(cfg_module.store_path(REPO, cfg.code_dir))
|
|
190
|
+
main_client = OmnigraphClient(store, cfg.queries_dir)
|
|
191
|
+
assert view in main_client.list_branches()
|
|
192
|
+
assert "feature_new-api" not in main_client.list_branches(), (
|
|
193
|
+
"the un-namespaced name is the collision this scheme replaced"
|
|
194
|
+
)
|
|
195
|
+
assert owner(view) == actor
|
|
196
|
+
|
|
197
|
+
branch_client = OmnigraphClient(store, cfg.queries_dir, branch=view)
|
|
198
|
+
assert branch_client.read(
|
|
199
|
+
"code_read.gq", "find_by_name", {"name": "branch_only_symbol"}
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
158
203
|
@requires_stack
|
|
159
204
|
def test_branch_index_writes_to_bridge_overlay_not_main(tmp_path, monkeypatch):
|
|
160
205
|
"""A non-default branch's bridge writes land on its repo-qualified
|