witan-code 0.17.0__tar.gz → 0.19.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.17.0 → witan_code-0.19.0}/CHANGELOG.md +111 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/PKG-INFO +3 -3
- {witan_code-0.17.0 → witan_code-0.19.0}/docs/CLI_REFERENCE.md +17 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/pyproject.toml +25 -4
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_branches.py +73 -3
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_cli_dispatch.py +34 -2
- witan_code-0.19.0/tests/test_cli_selected_target.py +86 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_config_targets.py +75 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_context.py +30 -0
- witan_code-0.19.0/tests/test_index_reports_its_store.py +122 -0
- witan_code-0.19.0/tests/test_index_requires_a_repo_key.py +67 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_indexer.py +3 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_ingest.py +16 -0
- witan_code-0.19.0/tests/test_rebuild_honours_the_repo_key.py +90 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_remote_store.py +37 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_repo.py +65 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_setup.py +8 -1
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/cli.py +104 -30
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/context.py +1 -2
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/graph.py +11 -1
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/indexer.py +70 -8
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/ingest.py +2 -1
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/repo.py +26 -3
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/schema/code-schema.pg +11 -6
- witan_code-0.19.0/witan_code/selected_target.py +28 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/server.py +10 -3
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/setup.py +17 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/store.py +38 -1
- {witan_code-0.17.0 → witan_code-0.19.0}/.gitignore +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/README.md +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/config/claude.json +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/config/copilot.json +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/config/pi.json +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/conftest.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/docs/BRANCH_INDEXING.md +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/docs/EDGE_PRECISION_TIERS.md +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/docs/PACKAGE_MAP.md +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/docs/STAGE2_STITCHING.md +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/docs/SYMBOL_FORMAT.md +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/docs/SYMBOL_TABLE.md +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/docs/USER_GUIDE.md +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/install.sh +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/__init__.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/conftest.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_batched_deletes.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_bridge.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_chunked_load.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_cli_hooks.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_cli_output_format.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_config.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_crossrepo.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_edges.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_elicit.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_enrichment.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_github_app.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_graph.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_hooks.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_identity.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_index_failure_diagnostics.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_indexer_scope.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_languages.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_maintenance.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_package_map.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_reaper.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_remote_proxy.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_server.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_stitch.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_store.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_symbol_table.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_tasks.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_tool_schema.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_views.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/tests/test_visualize.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/__init__.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/__main__.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/bridge.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/bridge_extractors.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/config.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/edges.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/elicit.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/extensions/pi/codegraph.ts +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/github_app.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/hooks.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/identity.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/maintenance.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/output.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/package_map.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/queries/bridge.gq +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/queries/code_mutations.gq +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/queries/code_read.gq +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/queries/delete.gq +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/queries_ts/bash.scm +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/queries_ts/hcl.scm +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/queries_ts/python.scm +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/queries_ts/sql.scm +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/queries_ts/typescript.scm +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/queries_ts/yaml.scm +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/reaper.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/remote/__init__.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/remote/oidc.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/remote/proxy.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/remote/store.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/schema/bridge-schema.pg +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/skills/witan-code/SKILL.md +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/stitch.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/views.py +0 -0
- {witan_code-0.17.0 → witan_code-0.19.0}/witan_code/visualize.py +0 -0
|
@@ -6,6 +6,117 @@ 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
|
+
## [0.19.0] - 2026-09-16
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **Every code-graph edge type is keyed on `(@src, @dst)`.** A repeated
|
|
16
|
+
`Calls`/`References`/`Imports` pair upserts instead of appending. Needs
|
|
17
|
+
omnigraph 0.11; code graphs are rebuilt by reindexing. The indexer's
|
|
18
|
+
`_dedupe` stays, because a duplicate pair inside one load now fails the whole
|
|
19
|
+
batch.
|
|
20
|
+
|
|
21
|
+
- **`ClusterGraphMissing` and `IngestRefused` are logged at WARNING, not
|
|
22
|
+
reported to Sentry.** Both are now `witan_core.refusal.Refusal`s, so fastmcp
|
|
23
|
+
logs them at WARNING with no traceback and the client receives the message
|
|
24
|
+
unchanged. Both are still `RuntimeError`s. Requires witan-core 0.35.
|
|
25
|
+
|
|
26
|
+
- **Indexing a target with no resolvable repo key is refused, not guessed.**
|
|
27
|
+
When no `origin` remote resolved, the indexer fell back to the target's
|
|
28
|
+
directory name and wrote a permanent store into the shared per-repo store
|
|
29
|
+
directory under it. That key is neither unique — two `/tmp/*/tests` layouts
|
|
30
|
+
collide on `tests`, one silently overwriting the other's index — nor
|
|
31
|
+
recognizable next to real repo graphs; 36 such stores accumulated on one
|
|
32
|
+
machine, named `out2`, `tmp`, `data`, `sf`. Indexing something with no remote
|
|
33
|
+
is nearly always accidental (a scratch checkout, a temp repro layout), so the
|
|
34
|
+
write path now raises `repo.RepoNotDetected` naming the two opt-ins.
|
|
35
|
+
`witan code index`/`reindex` gained `--repo <uri>` alongside the existing
|
|
36
|
+
`WITAN_REPO`. **Reads of a local store are unaffected**: `repo.detect()` keeps
|
|
37
|
+
the directory fallback by default, so local stores already created under a
|
|
38
|
+
bare name stay reachable. Reads against a cluster drop it (see Fixed). The
|
|
39
|
+
hook paths (`session-init`, the PostToolUse reindex) already
|
|
40
|
+
swallow indexing errors, so this degrades to a no-op there rather than
|
|
41
|
+
surfacing.
|
|
42
|
+
|
|
43
|
+
- **`reindex --rebuild` resolves the repo key before it deletes anything, and
|
|
44
|
+
deletes the store that key names.** `--rebuild` runs ahead of the index, and
|
|
45
|
+
it used to resolve its own key with the directory-name fallback — so
|
|
46
|
+
`--rebuild --repo <uri>` on a remoteless checkout checked and deleted the
|
|
47
|
+
legacy bare-name graph rather than the one it was told to use. Worse, the
|
|
48
|
+
refusal above lived only inside `index_path`, which runs afterwards: with
|
|
49
|
+
`--yes` skipping the confirmation prompt, a run that was about to be refused
|
|
50
|
+
could still drop the shared bridge graph first. The key is now resolved once,
|
|
51
|
+
up front, by `indexer.resolve_repo_key` and passed into the rebuild.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- **Reads against a cluster no longer probe a graph named after the checkout's
|
|
56
|
+
directory.** In a checkout with no `origin` remote, `repo.detect()` falls back
|
|
57
|
+
to the directory name, and reads used that key against shared graphs too,
|
|
58
|
+
where no graph is keyed that way. Every prompt in a scratch directory sent
|
|
59
|
+
`code_store_views` for a graph like `code-scratchpad`, `code-tmp` or
|
|
60
|
+
`code-memory`, which made up most `ClusterGraphMissing` events in Sentry.
|
|
61
|
+
`store.detect_repo(cfg)` turns the fallback off when `cfg.is_cluster`. The
|
|
62
|
+
server's read path, the `inject-context` hook and `symbols` use it, and
|
|
63
|
+
`symbols` also turns it off whenever it dispatches to a deployment, which a
|
|
64
|
+
target with only `remote_url` does while keeping a local code store. Local
|
|
65
|
+
stores keep the fallback.
|
|
66
|
+
|
|
67
|
+
- **Two concurrent `code_store_open` calls for one view no longer fail the
|
|
68
|
+
second.** `ensure_branch` listed branches and then ran `branch create`, so two
|
|
69
|
+
callers could both see the view missing, and the loser failed with `branch
|
|
70
|
+
'<view>' already exists`. A create that fails that way now counts as success,
|
|
71
|
+
once a fresh listing confirms the branch is there. Any other create failure
|
|
72
|
+
still raises.
|
|
73
|
+
|
|
74
|
+
## [0.18.0] - 2026-09-02
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
|
|
78
|
+
- **`witan --target <name> code index` reaches this package.** witan-council's
|
|
79
|
+
launcher gained an app-level `--target`, and `witan code …` mounts this
|
|
80
|
+
package's cyclopts App but *not* its meta launcher, so the value is forwarded
|
|
81
|
+
into `witan_code.selected_target` and read by `_srv()`. Before this the flag
|
|
82
|
+
was bound but never routed: `cli._srv()` called `load_remote_config()` with no
|
|
83
|
+
argument, so `witan --target ci code repos` answered from the ambient target
|
|
84
|
+
while appearing to work. Standalone `witan-code --target <name> …` works too.
|
|
85
|
+
|
|
86
|
+
- **`witan code index` prints the store it wrote to, unconditionally.** The one
|
|
87
|
+
command whose entire output is about where symbols went was silent about which
|
|
88
|
+
graph received them — and memory and code graphs are routed by separate
|
|
89
|
+
settings, so a target whose memory is deployed can still index onto the
|
|
90
|
+
laptop. Printed for local and shared alike: a line that appears only in the
|
|
91
|
+
bad case is one nobody has learned to look for. Carried on `IndexStats` rather
|
|
92
|
+
than recomputed by the printer, so a *failed* run reports the same
|
|
93
|
+
destination — the run where "which graph was this?" is actually open.
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
|
|
97
|
+
- **`witan-code doctor` could not say which graph was broken under
|
|
98
|
+
`code_transport = "mcp"`.** The identifying column took `os.path.basename` of
|
|
99
|
+
the store address, which is right for a local path and wrong for an
|
|
100
|
+
MCP-routed one — those end in the endpoint URL, so all of production's
|
|
101
|
+
per-repo cluster graphs rendered as the identical string `mcp)`. `StoreRef`
|
|
102
|
+
gained a `label` that answers "which graph is this row" for all three address
|
|
103
|
+
forms, and `code_store_health` returns it alongside the full `store`.
|
|
104
|
+
|
|
105
|
+
- **A failed index did not always name its destination.** `IndexStats` was built
|
|
106
|
+
*after* `ensure_branch()` and the existing-hash read, both of which are remote
|
|
107
|
+
calls, so an unreachable endpoint or a rejected token raised before the stats
|
|
108
|
+
existed and the summary had nothing to print. Stats are now built straight
|
|
109
|
+
after the store resolves, and those two setup calls are wrapped so a failure
|
|
110
|
+
arrives as `IndexFailed` carrying the destination. `check_writable` is
|
|
111
|
+
deliberately left outside: it raises `SharedGraphWriteRefused`, a clean
|
|
112
|
+
self-explanatory refusal that callers assert on by type, not the unattributed
|
|
113
|
+
failure the phases exist to annotate.
|
|
114
|
+
|
|
115
|
+
- **`code_transport` is parsed off a target block.** A target registered with
|
|
116
|
+
`--remote-url` now carries `code_transport = "mcp"`, so branches indexed from
|
|
117
|
+
a checkout land on the cluster where another session — and another developer —
|
|
118
|
+
can see them, instead of in a directory on one laptop.
|
|
119
|
+
|
|
9
120
|
## [0.17.0] - 2026-09-01
|
|
10
121
|
|
|
11
122
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: witan-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.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.7
|
|
14
14
|
Requires-Dist: cyclopts<5,>=4
|
|
15
15
|
Requires-Dist: fastmcp<5,>=4
|
|
16
16
|
Requires-Dist: joserfc<2,>=1.0
|
|
@@ -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.36
|
|
30
30
|
Provides-Extra: tasks
|
|
31
31
|
Requires-Dist: fastmcp-tasks<5,>=4; extra == 'tasks'
|
|
32
32
|
Description-Content-Type: text/markdown
|
|
@@ -38,12 +38,21 @@ unchanged since the last index are skipped.
|
|
|
38
38
|
| Argument | Type | Default | Notes |
|
|
39
39
|
|---|---|---|---|
|
|
40
40
|
| `PATH` / `--path` | `Path` | `.` | Positional or `--path`; file or directory |
|
|
41
|
+
| `--repo` | `str` | detected | Canonical repo URI to key the code graph on |
|
|
41
42
|
|
|
42
43
|
Prints a summary line: `scanned/indexed/skipped/symbols/edges/bindings/errors`.
|
|
43
44
|
|
|
45
|
+
**A repo key is required.** It comes from the `origin` remote, or from `--repo`
|
|
46
|
+
/ `WITAN_REPO`. When none resolves, indexing is **refused** rather than filed
|
|
47
|
+
under the target's directory name — that key is not unique (two `/tmp/*/tests`
|
|
48
|
+
layouts collide on `tests`) and leaves a permanent store in the shared code
|
|
49
|
+
directory alongside real per-repo graphs. Reads still fall back to the
|
|
50
|
+
directory name, so stores created under one before this remain reachable.
|
|
51
|
+
|
|
44
52
|
```bash
|
|
45
53
|
witan-code index # index the whole repo, cwd
|
|
46
54
|
witan-code index app/models.py # index a single file
|
|
55
|
+
witan-code index /tmp/scratch --repo https://github.com/org/name # no origin
|
|
47
56
|
```
|
|
48
57
|
|
|
49
58
|
## `reindex [PATH]`
|
|
@@ -54,6 +63,14 @@ re-parsed and re-inserted even if unchanged.
|
|
|
54
63
|
| Argument | Type | Default | Notes |
|
|
55
64
|
|---|---|---|---|
|
|
56
65
|
| `PATH` / `--path` | `Path` | `.` | Positional or `--path` |
|
|
66
|
+
| `--rebuild` | `bool` | `false` | Delete unreadable stores before indexing |
|
|
67
|
+
| `--yes` | `bool` | `false` | Skip `--rebuild`'s confirmation prompt |
|
|
68
|
+
| `--repo` | `str` | detected | Canonical repo URI to key the code graph on |
|
|
69
|
+
|
|
70
|
+
Same repo-key requirement as `index` above. The key is resolved **before**
|
|
71
|
+
`--rebuild` deletes anything, and the rebuild deletes the store that key names —
|
|
72
|
+
so a run that would be refused deletes nothing, and `--rebuild --repo <uri>`
|
|
73
|
+
cannot drop a different graph than the one it is about to write.
|
|
57
74
|
|
|
58
75
|
```bash
|
|
59
76
|
witan-code reindex # rebuild the whole repo's store
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "witan-code"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.19.0"
|
|
4
4
|
description = "witan-code — tree-sitter code graph + cross-repo bridge (mounts under `witan code`)"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "BSD-3-Clause"
|
|
@@ -10,7 +10,14 @@ classifiers = [
|
|
|
10
10
|
"Programming Language :: Python :: 3",
|
|
11
11
|
]
|
|
12
12
|
dependencies = [
|
|
13
|
-
|
|
13
|
+
# >=0.7: witan_code/setup.py passes `mcp_servers_by_platform` to
|
|
14
|
+
# `RegistrationBundle`, a field added in agent-config-kit 0.7.0. It is a
|
|
15
|
+
# dataclass, so an external install resolving 0.4–0.6 raises TypeError on
|
|
16
|
+
# the unexpected keyword the moment `witan-code setup` builds its bundle.
|
|
17
|
+
# Same blind spot as the witan-core floors below — the workspace resolves
|
|
18
|
+
# agent-config-kit by path — except there is NO `check-core-floor`
|
|
19
|
+
# equivalent for this dependency, so this line is the only guard.
|
|
20
|
+
"agent-config-kit>=0.7,<1",
|
|
14
21
|
# `remote`: the ADR-0005 client stack (OIDC device grant + MCP-client
|
|
15
22
|
# proxy) behind `witan-code login` and the read commands' remote mode.
|
|
16
23
|
# Costs nothing extra in practice — fastmcp is already required here and
|
|
@@ -121,7 +128,21 @@ dependencies = [
|
|
|
121
128
|
# branches were open. Both times the pyproject line auto-merged without a
|
|
122
129
|
# conflict, since both sides named the same number. Re-check it by hand on
|
|
123
130
|
# every rebase; a floor one release low here is silent.
|
|
124
|
-
|
|
131
|
+
#
|
|
132
|
+
# ★ >=0.34 IS AN IMPORT FLOOR, the ordinary kind: `cli.py` imports
|
|
133
|
+
# `with_console_ctx` from `witan_core.elicit`, added in witan-core 0.34.0 in
|
|
134
|
+
# the same change as this caller. Caught by `just check-core-floor` on PR
|
|
135
|
+
# CI, which is exactly the case that check exists for.
|
|
136
|
+
#
|
|
137
|
+
# ★ >=0.35 IS AN IMPORT FLOOR too: `store.py` and `ingest.py` import
|
|
138
|
+
# `witan_core.refusal`, added in witan-core 0.35.0 in the same change.
|
|
139
|
+
#
|
|
140
|
+
# ★ >=0.36 IS A FORMAT FLOOR, and the silent kind: nothing new is imported,
|
|
141
|
+
# but 0.36.0 is the release that installs omnigraph 0.11, the only binary
|
|
142
|
+
# that parses the keyed `code-schema.pg`, and restores the null fields 0.11
|
|
143
|
+
# drops from query rows. An older witan-core imports fine and then fails
|
|
144
|
+
# on the first schema init.
|
|
145
|
+
"witan-core[cli,remote,observability,sentry]>=0.36,<1",
|
|
125
146
|
"fastmcp>=4,<5",
|
|
126
147
|
"cyclopts>=4,<5",
|
|
127
148
|
# [targets.<name>] override models (config.py) — also transitively pulled
|
|
@@ -204,7 +225,7 @@ testpaths = ["tests"]
|
|
|
204
225
|
packages = ["witan_code"]
|
|
205
226
|
|
|
206
227
|
[tool.bumpversion]
|
|
207
|
-
current_version = "0.
|
|
228
|
+
current_version = "0.19.0"
|
|
208
229
|
allow_dirty = true
|
|
209
230
|
|
|
210
231
|
[[tool.bumpversion.files]]
|
|
@@ -4,6 +4,8 @@ import asyncio
|
|
|
4
4
|
import inspect
|
|
5
5
|
import subprocess
|
|
6
6
|
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
7
9
|
from witan_code import repo as repo_module
|
|
8
10
|
|
|
9
11
|
from .conftest import SAMPLE, requires_stack
|
|
@@ -155,6 +157,74 @@ def test_feature_branch_indexes_to_own_branch(tmp_path, monkeypatch):
|
|
|
155
157
|
assert not on_main, "main view must not see in-flight branch symbols"
|
|
156
158
|
|
|
157
159
|
|
|
160
|
+
@requires_stack
|
|
161
|
+
def test_a_view_opened_by_two_callers_at_once_is_not_an_error(tmp_path, monkeypatch):
|
|
162
|
+
"""Two concurrent `code_store_open` calls for one view both list branches
|
|
163
|
+
before either creates it, so the second create fails with "already exists"
|
|
164
|
+
(Sentry WITAN-4). The view exists, which is what both callers asked for."""
|
|
165
|
+
from witan_code import config as cfg_module
|
|
166
|
+
from witan_code import indexer
|
|
167
|
+
from witan_code.graph import OmnigraphClient
|
|
168
|
+
|
|
169
|
+
monkeypatch.setenv("WITAN_REPO", REPO)
|
|
170
|
+
monkeypatch.setenv("WITAN_CODE_DIR", str(tmp_path / "code"))
|
|
171
|
+
cfg = cfg_module.load()
|
|
172
|
+
base = _git_repo(tmp_path / "r")
|
|
173
|
+
(base / "svc.py").write_text(SAMPLE)
|
|
174
|
+
indexer.index_path(base, config=cfg)
|
|
175
|
+
store = str(cfg_module.store_path(REPO, cfg.code_dir))
|
|
176
|
+
|
|
177
|
+
winner = OmnigraphClient(store, cfg.queries_dir, branch="feature_race")
|
|
178
|
+
loser = OmnigraphClient(store, cfg.queries_dir, branch="feature_race")
|
|
179
|
+
stale = loser.list_branches()
|
|
180
|
+
winner.ensure_branch()
|
|
181
|
+
|
|
182
|
+
real_list = loser.list_branches
|
|
183
|
+
listings = []
|
|
184
|
+
|
|
185
|
+
def list_branches():
|
|
186
|
+
listings.append(None)
|
|
187
|
+
return stale if len(listings) == 1 else real_list()
|
|
188
|
+
|
|
189
|
+
monkeypatch.setattr(loser, "list_branches", list_branches)
|
|
190
|
+
loser.ensure_branch()
|
|
191
|
+
assert len(listings) == 2, "the loser should have tried to create, then re-listed"
|
|
192
|
+
assert "feature_race" in real_list()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@requires_stack
|
|
196
|
+
@pytest.mark.parametrize(
|
|
197
|
+
("error", "listed"),
|
|
198
|
+
[
|
|
199
|
+
("omnigraph branch failed (exit 1):\npermission denied", ["main"]),
|
|
200
|
+
# The message alone is not trusted: the branch has to actually be there.
|
|
201
|
+
(
|
|
202
|
+
"omnigraph branch failed (exit 1):\nbranch 'feature_race' already exists",
|
|
203
|
+
["main"],
|
|
204
|
+
),
|
|
205
|
+
],
|
|
206
|
+
)
|
|
207
|
+
def test_a_create_failure_that_is_not_the_race_still_raises(
|
|
208
|
+
tmp_path, monkeypatch, error, listed
|
|
209
|
+
):
|
|
210
|
+
from witan_code import config as cfg_module
|
|
211
|
+
from witan_code.graph import OmnigraphClient
|
|
212
|
+
|
|
213
|
+
monkeypatch.setenv("WITAN_CODE_DIR", str(tmp_path / "code"))
|
|
214
|
+
client = OmnigraphClient(
|
|
215
|
+
str(tmp_path / "store"), cfg_module.load().queries_dir, branch="feature_race"
|
|
216
|
+
)
|
|
217
|
+
monkeypatch.setattr(client, "list_branches", lambda: listed)
|
|
218
|
+
|
|
219
|
+
def fail(*_args, **_kwargs):
|
|
220
|
+
raise RuntimeError(error)
|
|
221
|
+
|
|
222
|
+
monkeypatch.setattr(client, "_run", fail)
|
|
223
|
+
with pytest.raises(RuntimeError) as excinfo:
|
|
224
|
+
client.ensure_branch()
|
|
225
|
+
assert str(excinfo.value) == error
|
|
226
|
+
|
|
227
|
+
|
|
158
228
|
@requires_stack
|
|
159
229
|
def test_a_logged_in_writer_owns_the_views_it_indexes(
|
|
160
230
|
tmp_path, monkeypatch, logged_in_actor
|
|
@@ -310,7 +380,7 @@ def test_cached_git_amortizes_repeated_calls_within_ttl(monkeypatch):
|
|
|
310
380
|
monkeypatch.setattr(
|
|
311
381
|
srv.repo_module,
|
|
312
382
|
"detect",
|
|
313
|
-
lambda: calls.__setitem__("detect", calls["detect"] + 1) or "r",
|
|
383
|
+
lambda **_kw: calls.__setitem__("detect", calls["detect"] + 1) or "r",
|
|
314
384
|
)
|
|
315
385
|
monkeypatch.setattr(
|
|
316
386
|
srv.repo_module,
|
|
@@ -330,7 +400,7 @@ def test_cached_git_refreshes_after_ttl(monkeypatch):
|
|
|
330
400
|
|
|
331
401
|
srv._git_context.clear()
|
|
332
402
|
values = iter(["a", "b"])
|
|
333
|
-
monkeypatch.setattr(srv.repo_module, "detect", lambda: next(values))
|
|
403
|
+
monkeypatch.setattr(srv.repo_module, "detect", lambda **_kw: next(values))
|
|
334
404
|
|
|
335
405
|
assert srv._cached_detect() == "a"
|
|
336
406
|
srv._git_context["detect"] = (
|
|
@@ -355,7 +425,7 @@ def test_git_context_survives_within_a_test(monkeypatch):
|
|
|
355
425
|
from witan_code import server as srv
|
|
356
426
|
|
|
357
427
|
srv._git_context.clear()
|
|
358
|
-
monkeypatch.setattr(srv.repo_module, "detect", lambda: LEAKED_REPO)
|
|
428
|
+
monkeypatch.setattr(srv.repo_module, "detect", lambda **_kw: LEAKED_REPO)
|
|
359
429
|
assert srv._cached_detect() == LEAKED_REPO
|
|
360
430
|
assert srv._git_context["detect"][1] == LEAKED_REPO
|
|
361
431
|
|
|
@@ -90,6 +90,38 @@ def test_symbols_resolves_the_repo_client_side(monkeypatch):
|
|
|
90
90
|
assert srv.calls[0][1]["repo"] == "https://github.com/test/detected"
|
|
91
91
|
|
|
92
92
|
|
|
93
|
+
@pytest.mark.parametrize(("remote", "fallback"), [(True, False), (False, True)])
|
|
94
|
+
def test_symbols_never_sends_a_directory_name_to_a_deployment(
|
|
95
|
+
monkeypatch, remote, fallback
|
|
96
|
+
):
|
|
97
|
+
"""A target with only `remote_url` keeps a local code store, so the store
|
|
98
|
+
config alone would allow the fallback; the remote dispatch must not."""
|
|
99
|
+
monkeypatch.delenv("WITAN_CODE_SERVER", raising=False)
|
|
100
|
+
monkeypatch.delenv("WITAN_CODE_TRANSPORT", raising=False)
|
|
101
|
+
srv = _stub(code_repo_symbols=[])
|
|
102
|
+
monkeypatch.setattr(cli_module, "_srv", lambda: srv)
|
|
103
|
+
monkeypatch.setattr(cli_module, "_is_remote", lambda: remote)
|
|
104
|
+
seen = []
|
|
105
|
+
|
|
106
|
+
def _detect(*_a, dirname_fallback=True, **_kw):
|
|
107
|
+
seen.append(dirname_fallback)
|
|
108
|
+
return "scratchproj" if dirname_fallback else None
|
|
109
|
+
|
|
110
|
+
monkeypatch.setattr("witan_code.repo.detect", _detect)
|
|
111
|
+
|
|
112
|
+
cli_module.symbols()
|
|
113
|
+
|
|
114
|
+
# Last, not only: the local path loads config, whose target match detects too.
|
|
115
|
+
assert seen[-1] is fallback
|
|
116
|
+
assert srv.calls == (
|
|
117
|
+
[]
|
|
118
|
+
if remote
|
|
119
|
+
else [
|
|
120
|
+
("code_repo_symbols", {"repo": "scratchproj", "role": None, "scheme": None})
|
|
121
|
+
]
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
93
125
|
def test_stitch_dispatches_to_precise_edges(monkeypatch, capsys):
|
|
94
126
|
srv = _stub(
|
|
95
127
|
code_precise_edges=[
|
|
@@ -320,7 +352,7 @@ def test_rebuild_refuses_a_path_that_is_not_the_repo_root(
|
|
|
320
352
|
monkeypatch.setattr(store_module, "discard_store", _boom)
|
|
321
353
|
|
|
322
354
|
with pytest.raises(SystemExit):
|
|
323
|
-
cli_module._rebuild_stores(sub, yes=True)
|
|
355
|
+
cli_module._rebuild_stores(sub, yes=True, slug="https://github.com/test/cg")
|
|
324
356
|
|
|
325
357
|
out = capsys.readouterr().out
|
|
326
358
|
assert "not the repo root" in out
|
|
@@ -352,6 +384,6 @@ def test_rebuild_refuses_a_file_path_with_real_git(tmp_path, monkeypatch, capsys
|
|
|
352
384
|
monkeypatch.setattr(store_module, "discard_store", _boom)
|
|
353
385
|
|
|
354
386
|
with pytest.raises(SystemExit):
|
|
355
|
-
cli_module._rebuild_stores(target, yes=True)
|
|
387
|
+
cli_module._rebuild_stores(target, yes=True, slug="https://github.com/test/cg")
|
|
356
388
|
|
|
357
389
|
assert "not the repo root" in capsys.readouterr().out
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""``witan code …`` dispatches to the target witan's launcher bound.
|
|
2
|
+
|
|
3
|
+
This package's App is mounted by witan's CLI but its meta launcher is not, so
|
|
4
|
+
witan's launcher forwards the ``--target`` it bound into
|
|
5
|
+
:mod:`witan_code.selected_target` (the same forwarding ``--output-format``
|
|
6
|
+
already gets, for the same reason). What is asserted here is the other end of
|
|
7
|
+
that wire: that the dispatch path actually consults it.
|
|
8
|
+
|
|
9
|
+
Resolving the ambient target here instead is not a cosmetic gap. ``witan code
|
|
10
|
+
index`` WRITES a code graph, so it would have written to whichever deployment
|
|
11
|
+
the checkout happened to match while the flag on the command line said
|
|
12
|
+
otherwise — and said nothing about it.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from witan_code import cli as code_cli
|
|
20
|
+
from witan_code import config as code_cfg
|
|
21
|
+
from witan_code.selected_target import selected_target, set_selected_target
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture(autouse=True)
|
|
25
|
+
def _clean_selection():
|
|
26
|
+
"""Module-level state, so a leaked value would silently steer a later test."""
|
|
27
|
+
set_selected_target(None)
|
|
28
|
+
yield
|
|
29
|
+
set_selected_target(None)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_dispatch_reads_the_forwarded_target(monkeypatch):
|
|
33
|
+
seen = {}
|
|
34
|
+
|
|
35
|
+
def _record(target=None):
|
|
36
|
+
seen["target"] = target
|
|
37
|
+
return None # in-process branch; a stub RemoteConfig is not the point
|
|
38
|
+
|
|
39
|
+
monkeypatch.setattr(code_cli, "_server", None)
|
|
40
|
+
monkeypatch.setattr(code_cfg, "load_remote_config", _record)
|
|
41
|
+
set_selected_target("qa")
|
|
42
|
+
|
|
43
|
+
code_cli._srv()
|
|
44
|
+
|
|
45
|
+
assert seen["target"] == "qa"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_no_forwarded_target_leaves_resolution_where_it_was(monkeypatch):
|
|
49
|
+
"""``None`` is not "no target": the standalone ``witan-code`` launcher
|
|
50
|
+
declares no ``--target``, and resolution falls through to ``WITAN_TARGET``
|
|
51
|
+
and the checkout's ``match_*`` rules exactly as it always did."""
|
|
52
|
+
seen = {}
|
|
53
|
+
|
|
54
|
+
def _record(target=None):
|
|
55
|
+
seen["target"] = target
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
monkeypatch.setattr(code_cli, "_server", None)
|
|
59
|
+
monkeypatch.setattr(code_cfg, "load_remote_config", _record)
|
|
60
|
+
|
|
61
|
+
code_cli._srv()
|
|
62
|
+
|
|
63
|
+
assert seen["target"] is None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_the_auth_path_reads_it_too(monkeypatch):
|
|
67
|
+
"""``witan code login`` has to reach the same deployment the read commands
|
|
68
|
+
do, or it mints a token against one target and spends it on another."""
|
|
69
|
+
seen = {}
|
|
70
|
+
|
|
71
|
+
def _record(target=None):
|
|
72
|
+
seen["target"] = target
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
monkeypatch.setattr(code_cfg, "load_remote_config", _record)
|
|
76
|
+
set_selected_target("ci")
|
|
77
|
+
|
|
78
|
+
with pytest.raises(SystemExit):
|
|
79
|
+
code_cli._remote_or_exit() # no remote configured -> clean exit
|
|
80
|
+
|
|
81
|
+
assert seen["target"] == "ci"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_accessor_round_trips():
|
|
85
|
+
set_selected_target("production")
|
|
86
|
+
assert selected_target() == "production"
|
|
@@ -377,3 +377,78 @@ def test_unknown_index_role_is_rejected(monkeypatch, toml_file):
|
|
|
377
377
|
|
|
378
378
|
with pytest.raises(ValueError, match="Unknown index_role"):
|
|
379
379
|
load()
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# ── The seam with `witan target add` ─────────────────────────────────────────
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def test_the_block_witan_target_add_writes_routes_code_graphs_to_the_cluster(
|
|
386
|
+
monkeypatch, toml_file
|
|
387
|
+
):
|
|
388
|
+
"""The exact block `witan target add --remote-url …` emits, read back here.
|
|
389
|
+
|
|
390
|
+
These are two packages agreeing by convention, not by a shared type: witan
|
|
391
|
+
(witan-council) renders the TOML, witan-code parses it, and nothing fails
|
|
392
|
+
loudly if they drift. Before `target add` learned `--code-transport`, a
|
|
393
|
+
deployed target had no `code_transport` key at all and fell through to the
|
|
394
|
+
`direct` default, leaving code graphs on the developer's own machine while
|
|
395
|
+
memory went to the deployment — silently, since a local store is a working
|
|
396
|
+
store. Keep this literal in step with `witan.cli.targets._FIELD_ORDER`.
|
|
397
|
+
"""
|
|
398
|
+
monkeypatch.setenv(
|
|
399
|
+
"WITAN_CONFIG",
|
|
400
|
+
toml_file(
|
|
401
|
+
"""
|
|
402
|
+
[targets.ol]
|
|
403
|
+
remote_url = "https://witan.ci.ol.mit.edu/mcp"
|
|
404
|
+
oidc_issuer = "https://sso-ci.ol.mit.edu/realms/ol-platform-engineering"
|
|
405
|
+
oidc_audience = "witan"
|
|
406
|
+
code_transport = "mcp"
|
|
407
|
+
match_orgs = ["mitodl"]
|
|
408
|
+
"""
|
|
409
|
+
),
|
|
410
|
+
)
|
|
411
|
+
monkeypatch.setenv("WITAN_REPO", "https://github.com/mitodl/agent-kit")
|
|
412
|
+
monkeypatch.delenv("WITAN_TARGET", raising=False)
|
|
413
|
+
monkeypatch.delenv("WITAN_CODE_TRANSPORT", raising=False)
|
|
414
|
+
monkeypatch.delenv("WITAN_CODE_SERVER", raising=False)
|
|
415
|
+
|
|
416
|
+
cfg = load()
|
|
417
|
+
assert cfg.target_name == "ol"
|
|
418
|
+
assert cfg.code_transport == "mcp"
|
|
419
|
+
# `mcp` alone makes it a cluster graph — code_server stays unset, because
|
|
420
|
+
# the deployment holds the address and resolves the actor from the JWT.
|
|
421
|
+
assert cfg.code_server is None
|
|
422
|
+
assert cfg.is_cluster
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def test_a_deployed_target_without_code_transport_still_falls_back_to_local(
|
|
426
|
+
monkeypatch, toml_file
|
|
427
|
+
):
|
|
428
|
+
"""The pre-fix shape, pinned so the regression is legible rather than silent.
|
|
429
|
+
|
|
430
|
+
This is what every target registered before 2026-09-01 looks like. It is
|
|
431
|
+
not an error and never was — which is the whole problem, and why the
|
|
432
|
+
onboarding doc now tells people to re-run `target add --force`.
|
|
433
|
+
"""
|
|
434
|
+
monkeypatch.setenv(
|
|
435
|
+
"WITAN_CONFIG",
|
|
436
|
+
toml_file(
|
|
437
|
+
"""
|
|
438
|
+
[targets.ol]
|
|
439
|
+
remote_url = "https://witan.ci.ol.mit.edu/mcp"
|
|
440
|
+
oidc_issuer = "https://sso-ci.ol.mit.edu/realms/ol-platform-engineering"
|
|
441
|
+
match_orgs = ["mitodl"]
|
|
442
|
+
"""
|
|
443
|
+
),
|
|
444
|
+
)
|
|
445
|
+
monkeypatch.setenv("WITAN_REPO", "https://github.com/mitodl/agent-kit")
|
|
446
|
+
monkeypatch.delenv("WITAN_TARGET", raising=False)
|
|
447
|
+
monkeypatch.delenv("WITAN_CODE_TRANSPORT", raising=False)
|
|
448
|
+
monkeypatch.delenv("WITAN_CODE_SERVER", raising=False)
|
|
449
|
+
|
|
450
|
+
cfg = load()
|
|
451
|
+
assert cfg.code_transport == "direct"
|
|
452
|
+
assert not cfg.is_cluster
|
|
453
|
+
# Memory is deployed while code graphs are not — the split this task fixed.
|
|
454
|
+
assert load_remote_config() is not None
|
|
@@ -6,9 +6,13 @@ check, store-exists branching), not the underlying query.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import os
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
9
11
|
import time
|
|
10
12
|
from pathlib import Path
|
|
11
13
|
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
12
16
|
from witan_code import context
|
|
13
17
|
|
|
14
18
|
|
|
@@ -45,6 +49,32 @@ def test_inject_context_empty_without_repo(tmp_path, monkeypatch):
|
|
|
45
49
|
assert context.inject_context() == ""
|
|
46
50
|
|
|
47
51
|
|
|
52
|
+
@pytest.mark.skipif(shutil.which("git") is None, reason="git not on PATH")
|
|
53
|
+
def test_inject_context_does_not_probe_a_dirname_graph_on_a_cluster(
|
|
54
|
+
tmp_path, monkeypatch
|
|
55
|
+
):
|
|
56
|
+
"""A checkout with no origin has no repo key when the store is a cluster.
|
|
57
|
+
|
|
58
|
+
Every prompt in a scratch directory used to probe ``code-scratchpad``,
|
|
59
|
+
which no provisioning ever declares (Sentry WITAN-G).
|
|
60
|
+
"""
|
|
61
|
+
monkeypatch.delenv("WITAN_REPO", raising=False)
|
|
62
|
+
monkeypatch.setenv("WITAN_CODE_SERVER", "https://omnigraph.example")
|
|
63
|
+
checkout = tmp_path / "scratchpad"
|
|
64
|
+
checkout.mkdir()
|
|
65
|
+
subprocess.run(["git", "init", "-q", str(checkout)], check=True)
|
|
66
|
+
monkeypatch.chdir(checkout)
|
|
67
|
+
probed = []
|
|
68
|
+
monkeypatch.setattr(
|
|
69
|
+
context.store_module.StoreRef,
|
|
70
|
+
"exists",
|
|
71
|
+
lambda self, config=None: probed.append(self) or False,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
assert context.inject_context() == ""
|
|
75
|
+
assert probed == []
|
|
76
|
+
|
|
77
|
+
|
|
48
78
|
def test_inject_context_empty_when_no_store_and_no_lock(tmp_path, monkeypatch):
|
|
49
79
|
monkeypatch.setenv("WITAN_REPO", "https://github.com/test/cg")
|
|
50
80
|
monkeypatch.setenv("WITAN_CODE_DIR", str(tmp_path / "code"))
|