witan-code 0.21.0__tar.gz → 0.23.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.21.0 → witan_code-0.23.0}/CHANGELOG.md +101 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/PKG-INFO +16 -7
- {witan_code-0.21.0 → witan_code-0.23.0}/README.md +12 -3
- {witan_code-0.21.0 → witan_code-0.23.0}/conftest.py +1 -1
- {witan_code-0.21.0 → witan_code-0.23.0}/docs/EDGE_PRECISION_TIERS.md +8 -6
- {witan_code-0.21.0 → witan_code-0.23.0}/docs/STAGE2_STITCHING.md +9 -4
- {witan_code-0.21.0 → witan_code-0.23.0}/pyproject.toml +15 -5
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_branches.py +1 -2
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_bridge.py +163 -59
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_cli_hooks.py +32 -3
- witan_code-0.23.0/tests/test_cli_output_format.py +210 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_cli_selected_target.py +0 -3
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_config_targets.py +0 -1
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_context.py +49 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_edges.py +1 -2
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_github_app.py +2 -1
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_hooks.py +6 -4
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_index_failure_diagnostics.py +22 -14
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_indexer_scope.py +1 -1
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_ingest.py +1 -2
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_remote_store.py +7 -3
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_repo.py +0 -1
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_setup.py +90 -2
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_store.py +4 -4
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_views.py +0 -1
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_visualize.py +4 -1
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/bridge.py +10 -6
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/bridge_extractors.py +71 -34
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/cli.py +85 -37
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/config.py +1 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/context.py +64 -8
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/extensions/pi/codegraph.ts +28 -7
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/github_app.py +2 -2
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/graph.py +2 -2
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/hooks.py +1 -1
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/identity.py +1 -1
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/indexer.py +14 -12
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/ingest.py +2 -2
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/remote/oidc.py +2 -1
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/remote/proxy.py +1 -1
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/remote/store.py +8 -7
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/server.py +8 -10
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/setup.py +15 -3
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/skills/witan-code/SKILL.md +29 -4
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/store.py +7 -7
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/visualize.py +6 -3
- witan_code-0.21.0/tests/test_cli_output_format.py +0 -95
- {witan_code-0.21.0 → witan_code-0.23.0}/.gitignore +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/changelog.d/README.md +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/config/claude.json +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/config/copilot.json +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/config/pi.json +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/docs/BRANCH_INDEXING.md +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/docs/CLI_REFERENCE.md +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/docs/PACKAGE_MAP.md +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/docs/SYMBOL_FORMAT.md +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/docs/SYMBOL_TABLE.md +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/docs/USER_GUIDE.md +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/install.sh +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/__init__.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/conftest.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_batched_deletes.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_chunked_load.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_cli_dispatch.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_config.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_crossrepo.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_elicit.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_enrichment.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_graph.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_identity.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_index_reports_its_store.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_index_requires_a_repo_key.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_indexer.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_languages.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_maintenance.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_package_map.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_reaper.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_rebuild_honours_the_repo_key.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_refusal_audit.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_remote_proxy.py +1 -1
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_server.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_stitch.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_symbol_table.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_tasks.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/tests/test_tool_schema.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/__init__.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/__main__.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/edges.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/elicit.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/maintenance.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/output.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/package_map.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/queries/bridge.gq +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/queries/code_mutations.gq +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/queries/code_read.gq +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/queries/delete.gq +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/queries_ts/bash.scm +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/queries_ts/hcl.scm +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/queries_ts/python.scm +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/queries_ts/sql.scm +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/queries_ts/typescript.scm +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/queries_ts/yaml.scm +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/reaper.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/remote/__init__.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/repo.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/schema/bridge-schema.pg +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/schema/code-schema.pg +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/selected_target.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/stitch.py +0 -0
- {witan_code-0.21.0 → witan_code-0.23.0}/witan_code/views.py +0 -0
|
@@ -8,6 +8,107 @@ a MINOR bump may include breaking changes).
|
|
|
8
8
|
|
|
9
9
|
<!-- scriv-insert-here -->
|
|
10
10
|
|
|
11
|
+
## [0.23.0] - 2026-09-28
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Python package providers.** A `pyproject.toml` whose directory holds a
|
|
16
|
+
`mitol/<pkg>/__init__.py` namespace child now provides `mitol.<pkg>`, so
|
|
17
|
+
`mitol.*` imports join to ol-django. Previously only `@mitodl/*` npm
|
|
18
|
+
packages had a provider extractor and no Python import could form an edge.
|
|
19
|
+
An existing graph gains these providers on ol-django's next full-repo
|
|
20
|
+
`witan-code index` (the provider files are not source files, so the
|
|
21
|
+
unchanged-file skip does not apply to them); `witan-code reindex` on it
|
|
22
|
+
works too.
|
|
23
|
+
|
|
24
|
+
- **Repo-level provider files that stop producing bindings lose their old
|
|
25
|
+
rows.** A `pyproject.toml`, `package.json`, Pulumi config or OpenAPI spec
|
|
26
|
+
that emitted nothing on this run was never purged, so on a run that could
|
|
27
|
+
not purge by the collected file list (e.g. indexing outside a repository
|
|
28
|
+
root) its previous bindings stayed. Every such file scanned is now purged
|
|
29
|
+
before its fresh bindings are written.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Allow cyclopts 5 (`cyclopts>=4,<6`). Under cyclopts 5, `--help` shows a
|
|
34
|
+
value placeholder after each option (e.g. `--target STR`), and a usage error
|
|
35
|
+
such as an unknown option exits with code 2 instead of 1.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **`witan code` listings emit `rows: []` on an empty result under
|
|
40
|
+
`--output-format`.** `symbols`, `stitch`, `stitch --unresolved` and `repos`
|
|
41
|
+
each returned early with a Rich sentence on stdout before the format was
|
|
42
|
+
consulted, so an empty result was not JSON/TOML/YAML. `_render_table` now
|
|
43
|
+
takes the empty message and prints it in `txt` mode only. `branches` never
|
|
44
|
+
consulted the format at all and now dumps `{repo, views, error}` rows, with
|
|
45
|
+
`--prune`'s progress lines on stderr under a structured format; the `repos`
|
|
46
|
+
unreadable-store warning and `doctor`'s summary and error lines move to
|
|
47
|
+
stderr there too.
|
|
48
|
+
- **Table titles and cells are escaped for Rich.** A symbol, path or title
|
|
49
|
+
containing `[...]` was read as markup and lost from the rendered table.
|
|
50
|
+
|
|
51
|
+
- **Endpoint bindings form cross-repo edges again.** Every endpoint consumer
|
|
52
|
+
scored 0.0 because the extractor only matches relative paths and the
|
|
53
|
+
`relative_url` heuristic subtracted 0.5 from the 0.5 baseline, so
|
|
54
|
+
`code_repo_dependencies` and the other heuristic-tier views filtered every
|
|
55
|
+
one out at their 0.5 cutoff. The `relative_url` penalty, its unreachable
|
|
56
|
+
`explicit_hostname` boost, and the `generated_file` penalty are removed. A
|
|
57
|
+
generated client for a repo's own API is still suppressed by
|
|
58
|
+
`self_provided_key`. Stored confidences are computed at index time, so
|
|
59
|
+
existing graphs need `witan-code reindex` (not `index`, which skips
|
|
60
|
+
unchanged files) on the consumer repos before these edges appear.
|
|
61
|
+
|
|
62
|
+
## [0.22.0] - 2026-09-25
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- **`witan-code inject-context --client pi`.** The status block told every
|
|
67
|
+
agent to run Claude's `ToolSearch` and then call `code_*` tools directly,
|
|
68
|
+
including under Pi, which has no `ToolSearch` and reaches MCP tools only
|
|
69
|
+
through pi-mcp-adapter's `mcp` proxy under a server-prefixed name. With
|
|
70
|
+
`--client pi` the block instead says to find the exact name with
|
|
71
|
+
`mcp({ search: "code_find_definition callers impact" })` and call it with
|
|
72
|
+
`mcp({ tool: "<name>", args: {...} })`, and points at `/skill:witan-code`.
|
|
73
|
+
The default (`--client claude`, what the Claude hook runs) is unchanged.
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
|
|
77
|
+
- **The Pi extension passes `--client pi`**, so a Pi install needs a
|
|
78
|
+
`witan-code` CLI from this release or later: an older one rejects the flag
|
|
79
|
+
and the extension, as with any failure, injects no block.
|
|
80
|
+
- **The `witan-code` skill's tool reference is client-neutral**, with short
|
|
81
|
+
notes on reaching the tools from Claude Code (`ToolSearch`) and Pi (the
|
|
82
|
+
`mcp` proxy).
|
|
83
|
+
|
|
84
|
+
- `witan-code setup --agent pi` (and `--agent all` when Pi is detected) now
|
|
85
|
+
warns when the pi-mcp-adapter Pi package is not declared in Pi's settings,
|
|
86
|
+
since Pi ignores the witan-code MCP entry in `~/.pi/agent/mcp.json` without
|
|
87
|
+
it. The warning comes from agent-config-kit's preflight via
|
|
88
|
+
`witan_core.cli.report_install`. The README and the `setup` help document
|
|
89
|
+
the prerequisite (`pi install npm:pi-mcp-adapter`).
|
|
90
|
+
|
|
91
|
+
- The `agent-config-kit` floor is raised from `>=0.7` to `>=0.10`, the first
|
|
92
|
+
release whose `InstallResult` carries the `prerequisites` that warning is
|
|
93
|
+
built from; on an older agent-config-kit the warning silently never
|
|
94
|
+
appeared.
|
|
95
|
+
|
|
96
|
+
- The `witan-core` floor is raised from `>=0.39` to `>=0.40`, the first
|
|
97
|
+
release whose `report_install` prints those `prerequisites`; on 0.39 the
|
|
98
|
+
server still runs but the warning is silently dropped.
|
|
99
|
+
|
|
100
|
+
### Fixed
|
|
101
|
+
|
|
102
|
+
- **The Pi extension now waits as long for the code-graph status block as
|
|
103
|
+
Claude does.** `codegraph.ts`'s `before_agent_start` gave `witan-code
|
|
104
|
+
inject-context` 5s, against the 15s the Claude `UserPromptSubmit` hook gets,
|
|
105
|
+
so a cold store read that ran past 5s was killed before it could fill the
|
|
106
|
+
cache, and that prompt, and the next cold one, got no block. Both
|
|
107
|
+
now read one value (`INJECT_CONTEXT_TIMEOUT_SECONDS` in `witan_code.setup`,
|
|
108
|
+
`INJECT_CONTEXT_TIMEOUT_MS` in the extension), and a test fails if they, or
|
|
109
|
+
the `configs/pi/extensions` mirror, drift. Every other Pi handler stays
|
|
110
|
+
detached, and a timeout still degrades to no context.
|
|
111
|
+
|
|
11
112
|
## [0.21.0] - 2026-09-23
|
|
12
113
|
|
|
13
114
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: witan-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.23.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.
|
|
14
|
-
Requires-Dist: cyclopts<
|
|
13
|
+
Requires-Dist: agent-config-kit<1,>=0.10
|
|
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
|
|
@@ -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.40
|
|
30
30
|
Provides-Extra: tasks
|
|
31
31
|
Requires-Dist: fastmcp-tasks<5,>=4; extra == 'tasks'
|
|
32
32
|
Description-Content-Type: text/markdown
|
|
@@ -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
|
|
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,6 +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.
|
|
307
|
+
|
|
302
308
|
If `witan` is *also* installed and witan-code is importable in that same
|
|
303
309
|
environment (e.g. via the `--with` in the `uv tool install`/MCP server's
|
|
304
310
|
`uvx` invocation), `witan setup` folds this same bundle in automatically —
|
|
@@ -715,7 +721,10 @@ A Pi equivalent of all four lives in one extension,
|
|
|
715
721
|
can tell "no cross-repo consumers" from "no cross-repo data"), and the
|
|
716
722
|
`ToolSearch` call that makes the `code_*` tools callable when the harness
|
|
717
723
|
delivers them deferred — followed by a `code_find_definition` →
|
|
718
|
-
`code_callers`/`code_impact` call template.
|
|
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.
|
|
719
728
|
Independent of `witan`'s own `inject-context` hook (no cross-package
|
|
720
729
|
coupling) — register it alone for a witan-code-only install. Prints
|
|
721
730
|
nothing when the repo has neither a store nor an index in flight.
|
|
@@ -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
|
|
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,6 +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.
|
|
274
|
+
|
|
269
275
|
If `witan` is *also* installed and witan-code is importable in that same
|
|
270
276
|
environment (e.g. via the `--with` in the `uv tool install`/MCP server's
|
|
271
277
|
`uvx` invocation), `witan setup` folds this same bundle in automatically —
|
|
@@ -682,7 +688,10 @@ A Pi equivalent of all four lives in one extension,
|
|
|
682
688
|
can tell "no cross-repo consumers" from "no cross-repo data"), and the
|
|
683
689
|
`ToolSearch` call that makes the `code_*` tools callable when the harness
|
|
684
690
|
delivers them deferred — followed by a `code_find_definition` →
|
|
685
|
-
`code_callers`/`code_impact` call template.
|
|
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.
|
|
686
695
|
Independent of `witan`'s own `inject-context` hook (no cross-package
|
|
687
696
|
coupling) — register it alone for a witan-code-only install. Prints
|
|
688
697
|
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:
|
|
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,
|
|
39
|
-
consumer_repo,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
|
@@ -45,10 +45,15 @@ way a written edge could.
|
|
|
45
45
|
|
|
46
46
|
```python
|
|
47
47
|
PreciseEdge(
|
|
48
|
-
consumer_repo,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "witan-code"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.23.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"
|
|
@@ -17,7 +17,11 @@ dependencies = [
|
|
|
17
17
|
# Same blind spot as the witan-core floors below — the workspace resolves
|
|
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`,
|
|
22
|
+
# added in 0.10.0. witan_core's `report_install` reads it with getattr, so
|
|
23
|
+
# an older resolution does not fail — it just silently drops the warning.
|
|
24
|
+
"agent-config-kit>=0.10,<1",
|
|
21
25
|
# `remote`: the ADR-0005 client stack (OIDC device grant + MCP-client
|
|
22
26
|
# proxy) behind `witan-code login` and the read commands' remote mode.
|
|
23
27
|
# Costs nothing extra in practice — fastmcp is already required here and
|
|
@@ -151,9 +155,15 @@ dependencies = [
|
|
|
151
155
|
# code-graph writes parallel agents lose to each other. And
|
|
152
156
|
# `ClusterGraphMissing` sets `log_safe_message`, which only 0.39 reads: on
|
|
153
157
|
# 0.38 its text never reaches the `mcp.tool_call` log line.
|
|
154
|
-
|
|
158
|
+
#
|
|
159
|
+
# ★ >=0.40 IS A BEHAVIOUR FLOOR too: `witan-code setup --agent pi` promises
|
|
160
|
+
# a warning when Pi lacks pi-mcp-adapter, and
|
|
161
|
+
# `witan_core.cli.report_install` only prints `InstallResult.prerequisites`
|
|
162
|
+
# from 0.40.0. It reads the field with getattr, so 0.39 imports and runs —
|
|
163
|
+
# and silently drops the warning.
|
|
164
|
+
"witan-core[cli,remote,observability,sentry]>=0.40,<1",
|
|
155
165
|
"fastmcp>=4,<5",
|
|
156
|
-
"cyclopts>=4,<
|
|
166
|
+
"cyclopts>=4,<6",
|
|
157
167
|
# [targets.<name>] override models (config.py) — also transitively pulled
|
|
158
168
|
# in by fastmcp, declared explicitly since config.py imports it directly.
|
|
159
169
|
"pydantic>=2,<3",
|
|
@@ -234,7 +244,7 @@ testpaths = ["tests"]
|
|
|
234
244
|
packages = ["witan_code"]
|
|
235
245
|
|
|
236
246
|
[tool.bumpversion]
|
|
237
|
-
current_version = "0.
|
|
247
|
+
current_version = "0.23.0"
|
|
238
248
|
allow_dirty = true
|
|
239
249
|
|
|
240
250
|
[[tool.bumpversion.files]]
|
|
@@ -14,128 +14,104 @@ from witan_code.bridge_extractors import (
|
|
|
14
14
|
_compute_file_confidence,
|
|
15
15
|
adjust_confidence,
|
|
16
16
|
extract_file_bindings,
|
|
17
|
+
extract_repo_bindings,
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
from .conftest import requires_stack
|
|
20
21
|
|
|
21
|
-
|
|
22
22
|
# ── Confidence scoring unit tests ─────────────────────────────────
|
|
23
23
|
# All tests operate on _compute_file_confidence directly; no omnigraph needed.
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def test_load_test_path_penalizes_score():
|
|
27
27
|
"""load_test_path penalty: file under a load_testing/ directory."""
|
|
28
|
-
score = _compute_file_confidence(
|
|
29
|
-
"load_testing/backend/client/v0/api.ts", "", "/api/v0/courses/"
|
|
30
|
-
)
|
|
28
|
+
score = _compute_file_confidence("load_testing/backend/client/v0/api.ts", "")
|
|
31
29
|
assert score < 0.5, f"load_test_path should penalize; got {score}"
|
|
32
30
|
|
|
33
31
|
|
|
34
32
|
def test_k6_path_segment_penalizes_score():
|
|
35
33
|
"""load_test_path penalty applies to /k6/ path segments."""
|
|
36
|
-
score = _compute_file_confidence("perf/k6/scenarios.ts", ""
|
|
34
|
+
score = _compute_file_confidence("perf/k6/scenarios.ts", "")
|
|
37
35
|
assert score < 0.5, f"k6 path segment should penalize; got {score}"
|
|
38
36
|
|
|
39
37
|
|
|
40
38
|
def test_load_test_framework_import_penalizes():
|
|
41
39
|
"""load_test_framework penalty: file imports k6."""
|
|
42
40
|
content = "import http from 'k6/http';\nimport { sleep } from 'k6';\n"
|
|
43
|
-
score = _compute_file_confidence("client.ts", content
|
|
41
|
+
score = _compute_file_confidence("client.ts", content)
|
|
44
42
|
assert score < 0.5, f"k6 framework import should penalize; got {score}"
|
|
45
43
|
|
|
46
44
|
|
|
47
45
|
def test_load_test_framework_locust_penalizes():
|
|
48
46
|
"""load_test_framework penalty applies to locust imports."""
|
|
49
47
|
content = "from 'locust' import HttpUser\n"
|
|
50
|
-
score = _compute_file_confidence("test_load.ts", content
|
|
48
|
+
score = _compute_file_confidence("test_load.ts", content)
|
|
51
49
|
assert score < 0.5, f"locust import should penalize; got {score}"
|
|
52
50
|
|
|
53
51
|
|
|
54
52
|
def test_load_test_framework_artillery_penalizes():
|
|
55
53
|
"""load_test_framework penalty applies to artillery imports."""
|
|
56
54
|
content = "import stuff from 'artillery';\n"
|
|
57
|
-
score = _compute_file_confidence("perf.ts", content
|
|
55
|
+
score = _compute_file_confidence("perf.ts", content)
|
|
58
56
|
assert score < 0.5, f"artillery import should penalize; got {score}"
|
|
59
57
|
|
|
60
58
|
|
|
61
|
-
def
|
|
62
|
-
"""
|
|
63
|
-
score = _compute_file_confidence("src/client.ts", "", "/api/v0/profiles/")
|
|
64
|
-
assert score < 0.5, f"relative URL should penalize; got {score}"
|
|
59
|
+
def test_plain_client_clears_the_edge_cutoff():
|
|
60
|
+
"""A consumer with no signal either way sits at the cutoff, so it forms an edge.
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
score = _compute_file_confidence("src/generated.ts", content, "/api/v0/courses/")
|
|
71
|
-
assert score < 0.1, f"generated file header should heavily penalize; got {score}"
|
|
62
|
+
Every path `_TS_ENDPOINT` extracts is relative; penalizing that put every
|
|
63
|
+
endpoint consumer below `build_graph`'s 0.5 cutoff.
|
|
64
|
+
"""
|
|
65
|
+
assert _compute_file_confidence("src/client.ts", "") == 0.5
|
|
72
66
|
|
|
73
67
|
|
|
74
|
-
def
|
|
75
|
-
"""
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
assert
|
|
68
|
+
def test_generated_client_is_not_penalized():
|
|
69
|
+
"""A generated OpenAPI client is exact consumer evidence; self_provided_key
|
|
70
|
+
handles one generated for the repo's own API."""
|
|
71
|
+
content = "// DO NOT EDIT — generated by openapi-generator\nexport const x = 1;"
|
|
72
|
+
assert _compute_file_confidence("src/generated.ts", content) == 0.5
|
|
79
73
|
|
|
80
74
|
|
|
81
75
|
def test_same_origin_helper_penalizes():
|
|
82
76
|
"""same_origin_helper penalty: relative import to a csrf/fetch wrapper."""
|
|
83
77
|
content = "import { fetchJSONWithCSRF } from '../utils/csrf';\n"
|
|
84
|
-
score = _compute_file_confidence("src/actions.ts", content
|
|
78
|
+
score = _compute_file_confidence("src/actions.ts", content)
|
|
85
79
|
assert score < 0.5, f"same-origin helper import should penalize; got {score}"
|
|
86
80
|
|
|
87
81
|
|
|
88
82
|
def test_same_origin_relative_api_client_penalizes():
|
|
89
83
|
"""same_origin_helper penalty: relative import to an api-client file."""
|
|
90
84
|
content = "import { apiFetch } from './api-client';\n"
|
|
91
|
-
score = _compute_file_confidence("src/actions.ts", content
|
|
85
|
+
score = _compute_file_confidence("src/actions.ts", content)
|
|
92
86
|
assert score < 0.5, f"relative api-client import should penalize; got {score}"
|
|
93
87
|
|
|
94
88
|
|
|
95
|
-
def test_explicit_hostname_boosts_score():
|
|
96
|
-
"""explicit_hostname boost: URL has scheme + host (absolute URL)."""
|
|
97
|
-
score = _compute_file_confidence(
|
|
98
|
-
"src/client.ts", "", "https://api.example.com/api/v0/courses/"
|
|
99
|
-
)
|
|
100
|
-
assert score > 0.5, f"explicit hostname should boost; got {score}"
|
|
101
|
-
|
|
102
|
-
|
|
103
89
|
def test_client_directory_boosts_score():
|
|
104
90
|
"""client_directory boost: file path contains /clients/."""
|
|
105
|
-
score = _compute_file_confidence("src/clients/courses.ts", ""
|
|
106
|
-
|
|
107
|
-
# relative URL still penalizes but client_directory partially offsets
|
|
108
|
-
baseline = _compute_file_confidence("src/other/courses.ts", "", "/api/v0/courses/")
|
|
91
|
+
score = _compute_file_confidence("src/clients/courses.ts", "")
|
|
92
|
+
baseline = _compute_file_confidence("src/other/courses.ts", "")
|
|
109
93
|
assert score > baseline, (
|
|
110
94
|
f"client dir path ({score}) should score higher than non-client ({baseline})"
|
|
111
95
|
)
|
|
112
96
|
|
|
113
97
|
|
|
114
|
-
def test_client_and_hostname_both_boost():
|
|
115
|
-
"""client_directory + explicit_hostname compound boost > 0.7."""
|
|
116
|
-
score = _compute_file_confidence(
|
|
117
|
-
"src/clients/courses.ts",
|
|
118
|
-
"",
|
|
119
|
-
"https://api.example.com/api/v0/courses/",
|
|
120
|
-
)
|
|
121
|
-
assert score > 0.7, f"client dir + hostname should push score > 0.7; got {score}"
|
|
122
|
-
|
|
123
|
-
|
|
124
98
|
def test_score_clamped_to_zero():
|
|
125
99
|
"""Multiple penalties cannot push score below 0.0."""
|
|
126
|
-
content = "
|
|
127
|
-
score = _compute_file_confidence("load_testing/k6/api.ts", content
|
|
100
|
+
content = "import { f } from '../csrf';\nimport http from 'k6/http';\n"
|
|
101
|
+
score = _compute_file_confidence("load_testing/k6/api.ts", content)
|
|
128
102
|
assert score == 0.0, f"overlapping penalties should clamp at 0.0; got {score}"
|
|
129
103
|
|
|
130
104
|
|
|
131
105
|
def test_score_clamped_to_one():
|
|
132
106
|
"""Score cannot exceed 1.0 even with all boosts stacking."""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
"https://
|
|
107
|
+
b = _ep_consumer(confidence=0.9)
|
|
108
|
+
adjust_confidence(
|
|
109
|
+
b,
|
|
110
|
+
consumer_repo="https://github.com/test/repo-a",
|
|
111
|
+
provider_keys=frozenset(),
|
|
112
|
+
has_known_provider_package=True,
|
|
137
113
|
)
|
|
138
|
-
assert
|
|
114
|
+
assert b.confidence == 1.0, f"score must clamp at 1.0; got {b.confidence}"
|
|
139
115
|
|
|
140
116
|
|
|
141
117
|
# ── Store-level adjust_confidence tests ───────────────────────────
|
|
@@ -312,6 +288,89 @@ def test_csrf_client_still_extracts_package_and_env():
|
|
|
312
288
|
assert b.confidence == 1.0
|
|
313
289
|
|
|
314
290
|
|
|
291
|
+
OPENAPI_SPEC = """\
|
|
292
|
+
openapi: 3.0.3
|
|
293
|
+
paths:
|
|
294
|
+
/api/v1/courses/{id}/:
|
|
295
|
+
get: {}
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _rows(bindings, repo):
|
|
300
|
+
return [
|
|
301
|
+
{
|
|
302
|
+
"kind": b.kind,
|
|
303
|
+
"key_norm": b.key_norm,
|
|
304
|
+
"role": b.role,
|
|
305
|
+
"repo": repo,
|
|
306
|
+
"confidence": b.confidence,
|
|
307
|
+
}
|
|
308
|
+
for b in bindings
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def test_extracted_endpoint_and_package_bindings_form_edges(tmp_path):
|
|
313
|
+
"""What the extractors actually emit must survive build_graph's cutoff.
|
|
314
|
+
|
|
315
|
+
Regression: every endpoint consumer scored 0.0 (relative_url penalty on
|
|
316
|
+
paths that are always relative), and no Python package provider was ever
|
|
317
|
+
emitted, so neither kind produced a single edge on the deployed graph.
|
|
318
|
+
"""
|
|
319
|
+
from witan_code import visualize
|
|
320
|
+
|
|
321
|
+
consumer = "https://github.com/test/frontend"
|
|
322
|
+
provider = "https://github.com/test/backend"
|
|
323
|
+
lib = "https://github.com/test/ol-django"
|
|
324
|
+
|
|
325
|
+
backend = tmp_path / "backend"
|
|
326
|
+
(backend / "openapi" / "specs").mkdir(parents=True)
|
|
327
|
+
(backend / "openapi" / "specs" / "v1.yaml").write_text(OPENAPI_SPEC)
|
|
328
|
+
ol_django = tmp_path / "ol-django"
|
|
329
|
+
pkg = ol_django / "src" / "common" / "mitol" / "common"
|
|
330
|
+
pkg.mkdir(parents=True)
|
|
331
|
+
(pkg / "__init__.py").write_text("")
|
|
332
|
+
(ol_django / "src" / "common" / "pyproject.toml").write_text(
|
|
333
|
+
'[project]\nname = "mitol-django-common"\n'
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
rows = (
|
|
337
|
+
_rows(
|
|
338
|
+
extract_file_bindings(NORMAL_TS_CLIENT, "typescript", "src/client.ts"),
|
|
339
|
+
consumer,
|
|
340
|
+
)
|
|
341
|
+
+ _rows(
|
|
342
|
+
extract_file_bindings(
|
|
343
|
+
"from mitol.common.utils import now_in_utc\n", "python", "app.py"
|
|
344
|
+
),
|
|
345
|
+
consumer,
|
|
346
|
+
)
|
|
347
|
+
+ _rows(extract_repo_bindings(backend, provider)[0], provider)
|
|
348
|
+
+ _rows(extract_repo_bindings(ol_django, lib)[0], lib)
|
|
349
|
+
)
|
|
350
|
+
graph = visualize.build_graph(rows)
|
|
351
|
+
|
|
352
|
+
assert dict(graph.edges[(consumer, provider)].kinds) == {"endpoint": 1}
|
|
353
|
+
assert dict(graph.edges[(consumer, lib)].kinds) == {"package": 1}
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def test_python_package_provider_only_for_namespace_children(tmp_path):
|
|
357
|
+
"""An app repo's own top-level packages are not published providers."""
|
|
358
|
+
app = tmp_path / "app"
|
|
359
|
+
(app / "courses").mkdir(parents=True)
|
|
360
|
+
(app / "courses" / "__init__.py").write_text("")
|
|
361
|
+
(app / "pyproject.toml").write_text('[project]\nname = "app"\n')
|
|
362
|
+
assert extract_repo_bindings(app, "https://github.com/test/app") == (
|
|
363
|
+
[],
|
|
364
|
+
{"pyproject.toml"},
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# A regular (non-namespace) `mitol` package is the app's own, not shared.
|
|
368
|
+
(app / "mitol" / "thing").mkdir(parents=True)
|
|
369
|
+
(app / "mitol" / "__init__.py").write_text("")
|
|
370
|
+
(app / "mitol" / "thing" / "__init__.py").write_text("")
|
|
371
|
+
assert extract_repo_bindings(app, "https://github.com/test/app")[0] == []
|
|
372
|
+
|
|
373
|
+
|
|
315
374
|
# Repo A (mit-learn-like): a Django settings consumer + a NextJS endpoint/env
|
|
316
375
|
# consumer.
|
|
317
376
|
A_SETTINGS = """\
|
|
@@ -472,10 +531,9 @@ def test_known_provider_package_boost_sourced_from_stage1_symbol_table(
|
|
|
472
531
|
re-derivation from raw InterfaceBinding rows — verify the confidence
|
|
473
532
|
boost actually lands end-to-end through a real index + bridge write.
|
|
474
533
|
|
|
475
|
-
D_CLIENT_TS's endpoint consumer
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
+0.3 to 0.3.
|
|
534
|
+
No file-level signal fires on D_CLIENT_TS's endpoint consumer, so it
|
|
535
|
+
starts at the 0.5 baseline. The co-located package import of the
|
|
536
|
+
repo-c-provided package should then boost it by +0.3 to 0.8.
|
|
479
537
|
"""
|
|
480
538
|
from witan_code import config as cfg_mod
|
|
481
539
|
from witan_code import indexer
|
|
@@ -513,7 +571,53 @@ def test_known_provider_package_boost_sourced_from_stage1_symbol_table(
|
|
|
513
571
|
if r["repo"] == "https://github.com/test/repo-d" and r["kind"] == "endpoint"
|
|
514
572
|
]
|
|
515
573
|
assert len(rows) == 1
|
|
516
|
-
assert rows[0]["confidence"] == pytest.approx(0.
|
|
574
|
+
assert rows[0]["confidence"] == pytest.approx(0.8)
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
@requires_stack
|
|
578
|
+
def test_tier_b_source_that_stops_emitting_loses_its_rows(tmp_path, monkeypatch):
|
|
579
|
+
"""A pyproject.toml whose mitol/<pkg>/ was deleted must not keep providing it.
|
|
580
|
+
|
|
581
|
+
The toml is unchanged and still on disk. On a run that cannot purge by the
|
|
582
|
+
collected file list (here: no repository root), nothing put it in the
|
|
583
|
+
purge set until Tier B started reporting the sources it scanned.
|
|
584
|
+
"""
|
|
585
|
+
import shutil
|
|
586
|
+
|
|
587
|
+
from witan_code import config as cfg_mod
|
|
588
|
+
from witan_code import indexer
|
|
589
|
+
from witan_code import server as srv
|
|
590
|
+
from witan_code.graph import OmnigraphClient
|
|
591
|
+
|
|
592
|
+
monkeypatch.setenv("WITAN_CODE_DIR", str(tmp_path / "code"))
|
|
593
|
+
monkeypatch.setenv("WITAN_REPO", "https://github.com/test/ol-django")
|
|
594
|
+
cfg = cfg_mod.load()
|
|
595
|
+
monkeypatch.setattr(srv, "cfg", cfg)
|
|
596
|
+
srv._clients.clear()
|
|
597
|
+
|
|
598
|
+
repo = tmp_path / "ol-django"
|
|
599
|
+
pkg = repo / "mitol" / "common"
|
|
600
|
+
pkg.mkdir(parents=True)
|
|
601
|
+
(pkg / "__init__.py").write_text("")
|
|
602
|
+
(repo / "pyproject.toml").write_text('[project]\nname = "mitol-django-common"\n')
|
|
603
|
+
|
|
604
|
+
client = OmnigraphClient(
|
|
605
|
+
str(cfg_mod.bridge_store_path(cfg.code_dir)), cfg.queries_dir
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
def providers():
|
|
609
|
+
return [
|
|
610
|
+
r["key_norm"]
|
|
611
|
+
for r in client.read("bridge.gq", "all_bindings", {})
|
|
612
|
+
if r["kind"] == "package" and r["role"] == "provider"
|
|
613
|
+
]
|
|
614
|
+
|
|
615
|
+
_index(srv, indexer, repo)
|
|
616
|
+
assert providers() == ["mitol.common"]
|
|
617
|
+
|
|
618
|
+
shutil.rmtree(pkg)
|
|
619
|
+
_index(srv, indexer, repo)
|
|
620
|
+
assert providers() == []
|
|
517
621
|
|
|
518
622
|
|
|
519
623
|
@requires_stack
|