stata-code 0.6.3__tar.gz → 0.6.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {stata_code-0.6.3 → stata_code-0.6.5}/CHANGELOG.md +55 -0
  2. {stata_code-0.6.3 → stata_code-0.6.5}/PKG-INFO +73 -13
  3. {stata_code-0.6.3 → stata_code-0.6.5}/PUBLISHING.md +45 -22
  4. {stata_code-0.6.3 → stata_code-0.6.5}/README.md +72 -12
  5. {stata_code-0.6.3 → stata_code-0.6.5}/SCHEMA.md +1 -1
  6. {stata_code-0.6.3 → stata_code-0.6.5}/docs/design/hard_timeout.md +19 -26
  7. {stata_code-0.6.3 → stata_code-0.6.5}/pyproject.toml +1 -1
  8. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/__init__.py +1 -1
  9. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/core/_pool.py +72 -14
  10. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/core/_runtime.py +105 -12
  11. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/core/runner.py +48 -40
  12. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/mcp/server.py +2 -22
  13. {stata_code-0.6.3 → stata_code-0.6.5}/tests/test_mcp.py +18 -15
  14. {stata_code-0.6.3 → stata_code-0.6.5}/tests/test_pool.py +62 -0
  15. stata_code-0.6.5/tests/test_runtime_discovery.py +232 -0
  16. stata_code-0.6.3/tests/test_runtime_discovery.py +0 -81
  17. {stata_code-0.6.3 → stata_code-0.6.5}/.gitignore +0 -0
  18. {stata_code-0.6.3 → stata_code-0.6.5}/LICENSE +0 -0
  19. {stata_code-0.6.3 → stata_code-0.6.5}/LICENSE-POLICY.md +0 -0
  20. {stata_code-0.6.3 → stata_code-0.6.5}/examples/01-basic-regression.md +0 -0
  21. {stata_code-0.6.3 → stata_code-0.6.5}/examples/02-did-card-krueger.md +0 -0
  22. {stata_code-0.6.3 → stata_code-0.6.5}/examples/03-graphs.md +0 -0
  23. {stata_code-0.6.3 → stata_code-0.6.5}/examples/04-multi-session.md +0 -0
  24. {stata_code-0.6.3 → stata_code-0.6.5}/examples/05-large-matrix.md +0 -0
  25. {stata_code-0.6.3 → stata_code-0.6.5}/examples/README.md +0 -0
  26. {stata_code-0.6.3 → stata_code-0.6.5}/schema/run_result.schema.json +0 -0
  27. {stata_code-0.6.3 → stata_code-0.6.5}/scripts/check_versions.py +0 -0
  28. {stata_code-0.6.3 → stata_code-0.6.5}/scripts/export_schema.py +0 -0
  29. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/core/__init__.py +0 -0
  30. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/core/_refs.py +0 -0
  31. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/core/errors.py +0 -0
  32. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/core/log_artifacts.py +0 -0
  33. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/core/notebook.py +0 -0
  34. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/core/run_index.py +0 -0
  35. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/core/schema.py +0 -0
  36. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/kernel/__init__.py +0 -0
  37. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/kernel/__main__.py +0 -0
  38. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/kernel/assets/logo-32x32.png +0 -0
  39. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/kernel/assets/logo-64x64.png +0 -0
  40. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/kernel/assets/logo-svg.svg +0 -0
  41. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/kernel/kernel.py +0 -0
  42. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/mcp/__init__.py +0 -0
  43. {stata_code-0.6.3 → stata_code-0.6.5}/stata_code/mcp/__main__.py +0 -0
  44. {stata_code-0.6.3 → stata_code-0.6.5}/tests/__init__.py +0 -0
  45. {stata_code-0.6.3 → stata_code-0.6.5}/tests/conftest.py +0 -0
  46. {stata_code-0.6.3 → stata_code-0.6.5}/tests/fixtures/.gitkeep +0 -0
  47. {stata_code-0.6.3 → stata_code-0.6.5}/tests/test_cancel.py +0 -0
  48. {stata_code-0.6.3 → stata_code-0.6.5}/tests/test_errors.py +0 -0
  49. {stata_code-0.6.3 → stata_code-0.6.5}/tests/test_kernel.py +0 -0
  50. {stata_code-0.6.3 → stata_code-0.6.5}/tests/test_log_artifacts.py +0 -0
  51. {stata_code-0.6.3 → stata_code-0.6.5}/tests/test_mcp_stdio.py +0 -0
  52. {stata_code-0.6.3 → stata_code-0.6.5}/tests/test_notebook.py +0 -0
  53. {stata_code-0.6.3 → stata_code-0.6.5}/tests/test_notebook_phase2.py +0 -0
  54. {stata_code-0.6.3 → stata_code-0.6.5}/tests/test_public_api.py +0 -0
  55. {stata_code-0.6.3 → stata_code-0.6.5}/tests/test_run_index.py +0 -0
  56. {stata_code-0.6.3 → stata_code-0.6.5}/tests/test_runner.py +0 -0
  57. {stata_code-0.6.3 → stata_code-0.6.5}/tests/test_schema.py +0 -0
  58. {stata_code-0.6.3 → stata_code-0.6.5}/tests/test_schema_artifact.py +0 -0
@@ -6,6 +6,61 @@ to semver-major.minor for the result schema (see `SCHEMA.md` §6).
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## 0.6.5 — 2026-05-22
10
+
11
+ ### Fixed
12
+
13
+ - **OpenAI tool-schema compatibility.** `notebook_locate` and
14
+ `notebook_insert_cell` no longer advertise top-level `oneOf` constraints in
15
+ their MCP input schemas. OpenAI rejects those schemas during tool
16
+ registration, while the server-side runtime guards still enforce the
17
+ "exactly one query/anchor" rules.
18
+
19
+ ## 0.6.4 — 2026-05-21
20
+
21
+ ### Added
22
+
23
+ - **Claude Code plugin marketplace manifest.** `.claude-plugin/marketplace.json`
24
+ + `.claude-plugin/plugin.json` expose the repo as a single-plugin
25
+ marketplace, so users can install everything (MCP server config + agent
26
+ skill) with `claude plugin marketplace add brycewang-stanford/stata-code`
27
+ followed by `claude plugin install stata-code`.
28
+ - **`stata-code` agent skill.** `skills/stata-code/SKILL.md` teaches Claude
29
+ the v1.0 RunResult schema, the 15 MCP tools, the token-economy defaults,
30
+ the 32-kind error taxonomy, and the diagnose-only vs. fix-and-rerun
31
+ workflows. The plugin manifest auto-installs it alongside the MCP
32
+ server.
33
+ - **VS Code install-hint probe.** On activation the extension now resolves
34
+ the configured `stata-code-mcp` candidate list against `PATH` and any
35
+ workspace `.venv` / `venv`; if nothing matches, a one-time notification
36
+ offers to copy the `pip install "stata-code[mcp]"` command or open the
37
+ install docs. "Don't show again" pins the dismissal to the installed
38
+ extension version. Backed by a new pure module `serverProbe.ts` with
39
+ unit tests.
40
+ - **README multi-client section.** Cursor, Claude Desktop, Cline,
41
+ Continue, Windsurf, and Antigravity now have their config-file paths
42
+ spelled out next to the shared `stata-code-mcp` JSON snippet, plus
43
+ guidance for project-venv absolute paths and `uvx` setups.
44
+ - **README cell + section reference.** Documents that the VS Code
45
+ extension recognizes both `* %%` Jupyter-style cell markers and
46
+ `**#` … `**######` six-level section headings in `.do` files, and how
47
+ each interacts with the code-lens and Outline view.
48
+ - **Open VSX publish step.** `vscode-release.yml` now publishes the VSIX
49
+ to Open VSX on every `vscode-v*` tag. The step is gated on
50
+ `OVSX_PAT` being set at runtime and runs with `continue-on-error: true`,
51
+ so a missing or expired token never blocks the primary VS Code
52
+ Marketplace publish.
53
+
54
+ ### Fixed
55
+
56
+ - **Subprocess worker JSON protocol hardening.** Worker processes now keep
57
+ their private JSON protocol fds separate from real stdin/stdout and
58
+ redirect the real fd 0/1 pair to `os.devnull` before importing the
59
+ runner. The parent reader also ignores blank protocol noise while still
60
+ failing on non-empty non-JSON output. This prevents a pystata/Stata
61
+ initialization newline from surfacing as
62
+ `adapter_crash: worker emitted non-JSON: '\n'`.
63
+
9
64
  ## 0.6.3 — 2026-05-10
10
65
 
11
66
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stata-code
3
- Version: 0.6.3
3
+ Version: 0.6.5
4
4
  Summary: Agent-native Stata bridge — one core, multiple frontends (MCP, Jupyter, VSCode)
5
5
  Project-URL: Homepage, https://github.com/brycewang-stanford/stata-code
6
6
  Project-URL: Repository, https://github.com/brycewang-stanford/stata-code
@@ -84,7 +84,7 @@ Description-Content-Type: text/markdown
84
84
  └─────────────┘ └────────────┘ └─────────────────┘
85
85
  ```
86
86
 
87
- **Status: v0.6 (May 2026)** — the core, MCP server, Jupyter kernel, and VS Code extension work end-to-end against Stata 18 MP. Current test suite: 329 passing tests across schema, runner, MCP, kernel, notebook, run-index, subprocess-pool, and VS Code modules. License: **MIT**.
87
+ **Status: v0.6 (May 2026)** — the core, MCP server, Jupyter kernel, and VS Code extension work end-to-end against Stata 18 MP. The test suite covers schema, runner, MCP, kernel, notebook, run-index, subprocess-pool, and VS Code modules; CI also checks linting, type safety, schema generation, package metadata, and VSIX packaging. License: **MIT**.
88
88
 
89
89
  Two workflows v0.6 explicitly supports for end users:
90
90
 
@@ -204,14 +204,34 @@ If you want the repair loop, say so explicitly. Otherwise, treat failed runs as
204
204
  If you prefer not to `pip install stata-code` globally, run it ephemerally through [`uv`](https://github.com/astral-sh/uv):
205
205
 
206
206
  ```bash
207
- claude mcp add stata-code --scope user -- uvx --from stata-code stata-code-mcp
207
+ claude mcp add stata-code --scope user -- uvx --from "stata-code[mcp]" stata-code-mcp
208
208
  ```
209
209
 
210
210
  `uvx` will resolve and cache `stata-code` on first launch. Note: `pystata` is **not** on PyPI, so it still has to be locatable on the host. The runner adds the standard Stata install path (e.g. `/Applications/Stata/utilities/pystata` on macOS) to `sys.path` automatically; if your Stata lives elsewhere, set `PYTHONPATH` in the env block.
211
211
 
212
- #### Manual JSON config (Cursor / Claude Desktop / fallback)
212
+ #### Claude Code via plugin marketplace
213
213
 
214
- For clients without a `mcp add` CLI, edit the config file directly (`~/.claude/mcp.json`, Cursor settings, Claude Desktop `claude_desktop_config.json`, etc.):
214
+ This repository also ships a Claude Code plugin manifest (`.claude-plugin/`). Once you've added the marketplace to your Claude Code config, two commands wire up both the MCP server and the agent skill that teaches Claude the v1.0 result schema:
215
+
216
+ ```bash
217
+ claude plugin marketplace add brycewang-stanford/stata-code
218
+ claude plugin install stata-code
219
+ ```
220
+
221
+ The plugin registers the `stata-code` MCP server and installs the [`stata-code` skill](skills/stata-code/SKILL.md) so Claude branches on `error.kind`, calls `get_log(ref)` lazily, and uses the notebook-edit tools without you re-explaining them every session.
222
+
223
+ #### Other MCP clients (Cursor / Claude Desktop / Cline / Continue / Windsurf / Antigravity)
224
+
225
+ Most non-Claude-Code MCP clients accept the same JSON snippet. Drop it into the client's MCP config file:
226
+
227
+ | Client | Config file |
228
+ | --- | --- |
229
+ | Claude Desktop | macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`; Windows: `%APPDATA%\Claude\claude_desktop_config.json` |
230
+ | Cursor | `~/.cursor/mcp.json` (user) or `<workspace>/.cursor/mcp.json` (project) |
231
+ | Windsurf | `~/.codeium/windsurf/mcp_config.json` |
232
+ | Cline (VS Code) | settings: `cline.mcpServers` |
233
+ | Continue | `~/.continue/config.json` under `experimental.modelContextProtocolServers` |
234
+ | Antigravity / generic | `~/.claude/mcp.json` or whatever the client documents |
215
235
 
216
236
  ```json
217
237
  {
@@ -223,12 +243,39 @@ For clients without a `mcp add` CLI, edit the config file directly (`~/.claude/m
223
243
  }
224
244
  ```
225
245
 
226
- Or run it as a module if the binary is not on `PATH`:
246
+ Or, when the binary is not on `PATH`, run it as a module:
227
247
 
228
248
  ```bash
229
249
  python -m stata_code.mcp
230
250
  ```
231
251
 
252
+ When `stata-code-mcp` lives inside a project virtualenv (recommended for reproducibility), point the client at the absolute path:
253
+
254
+ ```json
255
+ {
256
+ "mcpServers": {
257
+ "stata-code": {
258
+ "command": "/abs/path/to/.venv/bin/stata-code-mcp"
259
+ }
260
+ }
261
+ }
262
+ ```
263
+
264
+ For `uvx`-only setups, set `"command": "uvx"` and `"args": ["--from", "stata-code", "stata-code-mcp"]`.
265
+
266
+ #### MCP troubleshooting
267
+
268
+ If `stata_run` reports `adapter_crash` with `worker emitted non-JSON: '\n'`,
269
+ upgrade to `stata-code>=0.6.4`, then restart the MCP client so it launches a
270
+ fresh server process. Also check that the client is resolving the expected
271
+ `stata-code-mcp` binary; project virtualenv installs should use the absolute
272
+ `.venv/bin/stata-code-mcp` path instead of relying on a global `PATH` entry.
273
+
274
+ If an OpenAI-backed client reports `API Error: 400 Invalid schema for function
275
+ 'mcp__stata-code__notebook_insert_cell'` and mentions a top-level `oneOf`,
276
+ upgrade to `stata-code>=0.6.5`, then restart the MCP client. Older server
277
+ processes keep advertising the stale schema until they are restarted.
278
+
232
279
  The MCP server registers 15 tools:
233
280
 
234
281
  | Tool | Purpose |
@@ -239,7 +286,7 @@ The MCP server registers 15 tools:
239
286
  | `get_graph` | Fetch graph bytes behind a `graph://` ref (`ImageContent`) |
240
287
  | `get_matrix` | Fetch matrix payloads behind a `matrix://` ref |
241
288
  | `list_sessions` | Enumerate live sessions |
242
- | `cancel_session` | Cooperatively cancel the next `stata_run` for a session |
289
+ | `cancel_session` | Cancel a session; the subprocess-backed path terminates in-flight runs and short-circuits pending ones |
243
290
  | `reset_session` | Drop a session's data |
244
291
  | `notebook_outline` | Compact per-cell index of a `.ipynb` (cell_id, type, preview) |
245
292
  | `notebook_get_cell` | One cell's full source plus a token-economic outputs summary |
@@ -304,7 +351,20 @@ The companion extension is on the Marketplace as [`brycewang-stanford.stata-code
304
351
  code --install-extension brycewang-stanford.stata-code-vscode
305
352
  ```
306
353
 
307
- Or open the **Extensions** sidebar in VS Code and search `stata-code`.
354
+ Or open the **Extensions** sidebar in VS Code and search `stata-code`. The extension is also available from [Open VSX](https://open-vsx.org/) so Cursor, Windsurf, and other VS Code-compatible editors can install it without going through the Microsoft Marketplace.
355
+
356
+ On first activation the extension probes for `stata-code-mcp` on `PATH` (and in any workspace `.venv` / `venv`). If nothing resolves, it shows a one-time install hint with the exact `pip install "stata-code[mcp]"` command — choose **Don't show again** to silence it for the installed extension version.
357
+
358
+ #### Cell and section conventions
359
+
360
+ The extension recognizes two complementary structural markers inside `.do` files. Either can be mixed in the same file; they do not conflict.
361
+
362
+ | Marker | Purpose | Example |
363
+ | --- | --- | --- |
364
+ | `* %% [title]` | Cell boundary. Each marker gets a **▶ Run Cell** code-lens; "Run Cell" submits the lines between this marker and the next one. Compatible with the Jupyter-style cell convention used by `kylebutts/vscode-stata`. | `* %% 02 model fit` |
365
+ | `**# title` … `**###### title` | Section heading, 1–6 levels deep. Each heading gets a **▶ Run Section** code-lens and contributes to the Outline view. "Run Section" submits the heading through the next equal- or higher-level heading, matching the hierarchical execution model from `ZihaoVistonWang.stata-all-in-one`. | `**## DiD specification` |
366
+
367
+ `program define … end` blocks are also surfaced in the Outline, nested under whichever section contains them.
308
368
 
309
369
  The extension still requires the MCP extra on your system Python (`pip install "stata-code[mcp]"`), so that `stata-code-mcp` resolves on `PATH` and can import the MCP SDK. Stata 17+ and a valid Stata license are required as for any other frontend.
310
370
 
@@ -397,7 +457,7 @@ stata_code/
397
457
  - MCP server: 15 tools, including notebook navigation / search / atomic edits and the run-bundle index (`list_runs`)
398
458
  - Jupyter kernel: rewired to the v1.0 pipeline, kernel logos bundled
399
459
  - Matrix size cap + `get_matrix(ref)` for large matrices (>10k cells)
400
- - Cooperative cancellation: `cancel(session_id)` / MCP `cancel_session`
460
+ - Subprocess-backed hard timeout and cancellation for the public Python API and MCP server: `timeout_ms`, `cancel(session_id)`, and MCP `cancel_session`
401
461
  - Per-cell repair loop on `.ipynb` via `notebook_outline` / `notebook_get_cell` / `notebook_edit_cell` with optimistic-concurrency `expected_source` guards and `origin_cell_id` echo on `RunResult`
402
462
  - Persistent run bundles + `list_runs` query over `manifest.json` files (filter by cell / origin / session / since / ok)
403
463
  - JSON Schema artifact auto-generated from `schema.py`: [`schema/run_result.schema.json`](schema/run_result.schema.json)
@@ -407,8 +467,8 @@ stata_code/
407
467
  ### Next Up
408
468
 
409
469
  - Console fallback for Stata 11–16, re-implemented against the v1.0 schema
410
- - Hard timeout / mid-Stata interrupt; design and tradeoffs in [`docs/design/hard_timeout.md`](docs/design/hard_timeout.md)
411
- - Extra VS Code polish (esbuild bundle, lighter VSIX, command palette UX)
470
+ - Decide whether to move the Jupyter kernel from the direct in-process runner to the subprocess pool, or keep documenting the current interactivity-first tradeoff
471
+ - Extra VS Code polish: extension-host end-to-end tests, first-run diagnostics, and command palette UX
412
472
  - **v1.0** — Stable schema, broader Stata edition coverage
413
473
 
414
474
  See [SCHEMA.md §7](SCHEMA.md) for explicitly out-of-scope items.
@@ -419,9 +479,9 @@ See [SCHEMA.md §7](SCHEMA.md) for explicitly out-of-scope items.
419
479
 
420
480
  ```bash
421
481
  pip install -e ".[dev,mcp,kernel]"
422
- pytest # full suite (310 tests)
482
+ pytest # full suite, including Stata tests when Stata is available
423
483
  pytest -m "not stata_required" # CI subset; no Stata needed
424
- pytest -m "stata_required" -v # Stata-only integration tests
484
+ pytest -m "stata_required" -v # real-Stata integration tests only
425
485
  ```
426
486
 
427
487
  The `stata_required` marker tags the real-Stata integration tests. CI uses `pytest -m "not stata_required"` so it does not collect them. Locally without Stata, those tests skip cleanly with the `"pystata / Stata 17+ not available"` message.
@@ -1,8 +1,9 @@
1
- # Publishing `stata-code` to PyPI
1
+ # Publishing `stata-code` to TestPyPI and PyPI
2
2
 
3
- This project publishes to PyPI via **GitHub Actions Trusted Publishing** (OIDC).
4
- There are **no API tokens** stored in GitHub repository secrets — PyPI verifies
5
- the OIDC identity of the workflow run instead.
3
+ This project publishes to TestPyPI and PyPI via **GitHub Actions Trusted
4
+ Publishing** (OIDC). There are **no API tokens** stored in GitHub repository
5
+ secrets — each package index verifies the OIDC identity of the workflow run
6
+ instead.
6
7
 
7
8
  The release pipeline is in [`.github/workflows/release.yml`](.github/workflows/release.yml).
8
9
 
@@ -28,25 +29,34 @@ Trusted Publishing has two modes:
28
29
 
29
30
  Either path lands you in the same place after the first run.
30
31
 
31
- ### 2. Configure the Trusted Publisher on PyPI
32
+ ### 2. Configure the Trusted Publishers
32
33
 
33
- On the PyPI publishing page, add a GitHub publisher with these values:
34
+ PyPI and TestPyPI are separate package indexes, so configure both publisher
35
+ records independently:
34
36
 
35
- | Field | Value |
36
- | --- | --- |
37
- | Owner | `brycewang-stanford` |
38
- | Repository name | `stata-code` |
39
- | Workflow filename | `release.yml` |
40
- | Environment name | `pypi` |
37
+ | Site | Manage URL | Environment |
38
+ | --- | --- | --- |
39
+ | PyPI | <https://pypi.org/manage/project/stata-code/settings/publishing/> | `pypi` |
40
+ | TestPyPI | <https://test.pypi.org/manage/project/stata-code/settings/publishing/> | `testpypi` |
41
+
42
+ Use the same GitHub publisher values on both sites:
43
+
44
+ - Owner: `brycewang-stanford`
45
+ - Repository name: `stata-code`
46
+ - Workflow filename: `release.yml`
47
+ - Environment name: `pypi` or `testpypi`, matching the site above
41
48
 
42
49
  The environment name is **required** and must match the `environment.name`
43
- declared in `release.yml` (`pypi`). PyPI uses it as an extra constraint:
50
+ declared in `release.yml`. PyPI / TestPyPI use it as an extra constraint:
44
51
  even a malicious workflow change in the repo cannot publish unless it runs
45
- under that exact environment.
52
+ under the exact environment configured for that index.
53
+
54
+ ### 3. Create the GitHub Environments
46
55
 
47
- ### 3. Create the GitHub Environment
56
+ In the GitHub repo: **Settings → Environments → New environment**. Create both:
48
57
 
49
- In the GitHub repo: **Settings → Environments → New environment → name `pypi`**.
58
+ - `testpypi`
59
+ - `pypi`
50
60
 
51
61
  Recommended hardening (all in the environment settings page):
52
62
 
@@ -66,25 +76,38 @@ The environment doesn't need any secrets — Trusted Publishing handles auth.
66
76
 
67
77
  Once the one-time setup above is done, every release is just:
68
78
 
69
- 1. **Bump the version** in `pyproject.toml` (`[project] version`).
70
- 2. **Update `CHANGELOG.md`**: move the `[Unreleased]` entries under a new
79
+ 1. **Bump the version everywhere**:
80
+ - `pyproject.toml` `[project] version`
81
+ - `stata_code/__init__.py` → `__version__`
82
+ - `stata_code/mcp/server.py` → `__version__`
83
+ - `vscode/package.json` → `version`
84
+ - `vscode/package-lock.json` → root package `version`
85
+ 2. **Run the version guard** before tagging:
86
+ ```bash
87
+ python scripts/check_versions.py
88
+ ```
89
+ 3. **Update `CHANGELOG.md`**: move the `[Unreleased]` entries under a new
71
90
  `## [X.Y.Z] - YYYY-MM-DD` heading, leaving an empty `[Unreleased]` shell
72
91
  on top.
73
- 3. **Commit** the bump:
92
+ 4. **Commit** the bump:
74
93
  ```bash
75
- git add pyproject.toml CHANGELOG.md
94
+ git add pyproject.toml stata_code/__init__.py stata_code/mcp/server.py vscode/package.json vscode/package-lock.json CHANGELOG.md
76
95
  git commit -m "release: vX.Y.Z"
77
96
  ```
78
- 4. **Tag and push**:
97
+ 5. **Tag and push**:
79
98
  ```bash
80
99
  git tag vX.Y.Z
81
100
  git push origin main
82
101
  git push origin vX.Y.Z
83
102
  ```
84
- 5. Watch the **`release` workflow** under the Actions tab. It will:
103
+ 6. Watch the **`release` workflow** under the Actions tab. It will:
85
104
  - build the sdist + wheel
86
105
  - run `twine check` on the artifacts
106
+ - publish to TestPyPI under the `testpypi` environment (no token needed)
87
107
  - publish to PyPI under the `pypi` environment (no token needed)
108
+ - poll PyPI's per-version JSON endpoint so a failed trusted-publisher
109
+ publish marks the workflow run failed even though the publish job itself
110
+ is `continue-on-error`
88
111
  - create a GitHub Release for the tag with the wheel + sdist attached and
89
112
  auto-generated release notes
90
113
 
@@ -45,7 +45,7 @@
45
45
  └─────────────┘ └────────────┘ └─────────────────┘
46
46
  ```
47
47
 
48
- **Status: v0.6 (May 2026)** — the core, MCP server, Jupyter kernel, and VS Code extension work end-to-end against Stata 18 MP. Current test suite: 329 passing tests across schema, runner, MCP, kernel, notebook, run-index, subprocess-pool, and VS Code modules. License: **MIT**.
48
+ **Status: v0.6 (May 2026)** — the core, MCP server, Jupyter kernel, and VS Code extension work end-to-end against Stata 18 MP. The test suite covers schema, runner, MCP, kernel, notebook, run-index, subprocess-pool, and VS Code modules; CI also checks linting, type safety, schema generation, package metadata, and VSIX packaging. License: **MIT**.
49
49
 
50
50
  Two workflows v0.6 explicitly supports for end users:
51
51
 
@@ -165,14 +165,34 @@ If you want the repair loop, say so explicitly. Otherwise, treat failed runs as
165
165
  If you prefer not to `pip install stata-code` globally, run it ephemerally through [`uv`](https://github.com/astral-sh/uv):
166
166
 
167
167
  ```bash
168
- claude mcp add stata-code --scope user -- uvx --from stata-code stata-code-mcp
168
+ claude mcp add stata-code --scope user -- uvx --from "stata-code[mcp]" stata-code-mcp
169
169
  ```
170
170
 
171
171
  `uvx` will resolve and cache `stata-code` on first launch. Note: `pystata` is **not** on PyPI, so it still has to be locatable on the host. The runner adds the standard Stata install path (e.g. `/Applications/Stata/utilities/pystata` on macOS) to `sys.path` automatically; if your Stata lives elsewhere, set `PYTHONPATH` in the env block.
172
172
 
173
- #### Manual JSON config (Cursor / Claude Desktop / fallback)
173
+ #### Claude Code via plugin marketplace
174
174
 
175
- For clients without a `mcp add` CLI, edit the config file directly (`~/.claude/mcp.json`, Cursor settings, Claude Desktop `claude_desktop_config.json`, etc.):
175
+ This repository also ships a Claude Code plugin manifest (`.claude-plugin/`). Once you've added the marketplace to your Claude Code config, two commands wire up both the MCP server and the agent skill that teaches Claude the v1.0 result schema:
176
+
177
+ ```bash
178
+ claude plugin marketplace add brycewang-stanford/stata-code
179
+ claude plugin install stata-code
180
+ ```
181
+
182
+ The plugin registers the `stata-code` MCP server and installs the [`stata-code` skill](skills/stata-code/SKILL.md) so Claude branches on `error.kind`, calls `get_log(ref)` lazily, and uses the notebook-edit tools without you re-explaining them every session.
183
+
184
+ #### Other MCP clients (Cursor / Claude Desktop / Cline / Continue / Windsurf / Antigravity)
185
+
186
+ Most non-Claude-Code MCP clients accept the same JSON snippet. Drop it into the client's MCP config file:
187
+
188
+ | Client | Config file |
189
+ | --- | --- |
190
+ | Claude Desktop | macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`; Windows: `%APPDATA%\Claude\claude_desktop_config.json` |
191
+ | Cursor | `~/.cursor/mcp.json` (user) or `<workspace>/.cursor/mcp.json` (project) |
192
+ | Windsurf | `~/.codeium/windsurf/mcp_config.json` |
193
+ | Cline (VS Code) | settings: `cline.mcpServers` |
194
+ | Continue | `~/.continue/config.json` under `experimental.modelContextProtocolServers` |
195
+ | Antigravity / generic | `~/.claude/mcp.json` or whatever the client documents |
176
196
 
177
197
  ```json
178
198
  {
@@ -184,12 +204,39 @@ For clients without a `mcp add` CLI, edit the config file directly (`~/.claude/m
184
204
  }
185
205
  ```
186
206
 
187
- Or run it as a module if the binary is not on `PATH`:
207
+ Or, when the binary is not on `PATH`, run it as a module:
188
208
 
189
209
  ```bash
190
210
  python -m stata_code.mcp
191
211
  ```
192
212
 
213
+ When `stata-code-mcp` lives inside a project virtualenv (recommended for reproducibility), point the client at the absolute path:
214
+
215
+ ```json
216
+ {
217
+ "mcpServers": {
218
+ "stata-code": {
219
+ "command": "/abs/path/to/.venv/bin/stata-code-mcp"
220
+ }
221
+ }
222
+ }
223
+ ```
224
+
225
+ For `uvx`-only setups, set `"command": "uvx"` and `"args": ["--from", "stata-code", "stata-code-mcp"]`.
226
+
227
+ #### MCP troubleshooting
228
+
229
+ If `stata_run` reports `adapter_crash` with `worker emitted non-JSON: '\n'`,
230
+ upgrade to `stata-code>=0.6.4`, then restart the MCP client so it launches a
231
+ fresh server process. Also check that the client is resolving the expected
232
+ `stata-code-mcp` binary; project virtualenv installs should use the absolute
233
+ `.venv/bin/stata-code-mcp` path instead of relying on a global `PATH` entry.
234
+
235
+ If an OpenAI-backed client reports `API Error: 400 Invalid schema for function
236
+ 'mcp__stata-code__notebook_insert_cell'` and mentions a top-level `oneOf`,
237
+ upgrade to `stata-code>=0.6.5`, then restart the MCP client. Older server
238
+ processes keep advertising the stale schema until they are restarted.
239
+
193
240
  The MCP server registers 15 tools:
194
241
 
195
242
  | Tool | Purpose |
@@ -200,7 +247,7 @@ The MCP server registers 15 tools:
200
247
  | `get_graph` | Fetch graph bytes behind a `graph://` ref (`ImageContent`) |
201
248
  | `get_matrix` | Fetch matrix payloads behind a `matrix://` ref |
202
249
  | `list_sessions` | Enumerate live sessions |
203
- | `cancel_session` | Cooperatively cancel the next `stata_run` for a session |
250
+ | `cancel_session` | Cancel a session; the subprocess-backed path terminates in-flight runs and short-circuits pending ones |
204
251
  | `reset_session` | Drop a session's data |
205
252
  | `notebook_outline` | Compact per-cell index of a `.ipynb` (cell_id, type, preview) |
206
253
  | `notebook_get_cell` | One cell's full source plus a token-economic outputs summary |
@@ -265,7 +312,20 @@ The companion extension is on the Marketplace as [`brycewang-stanford.stata-code
265
312
  code --install-extension brycewang-stanford.stata-code-vscode
266
313
  ```
267
314
 
268
- Or open the **Extensions** sidebar in VS Code and search `stata-code`.
315
+ Or open the **Extensions** sidebar in VS Code and search `stata-code`. The extension is also available from [Open VSX](https://open-vsx.org/) so Cursor, Windsurf, and other VS Code-compatible editors can install it without going through the Microsoft Marketplace.
316
+
317
+ On first activation the extension probes for `stata-code-mcp` on `PATH` (and in any workspace `.venv` / `venv`). If nothing resolves, it shows a one-time install hint with the exact `pip install "stata-code[mcp]"` command — choose **Don't show again** to silence it for the installed extension version.
318
+
319
+ #### Cell and section conventions
320
+
321
+ The extension recognizes two complementary structural markers inside `.do` files. Either can be mixed in the same file; they do not conflict.
322
+
323
+ | Marker | Purpose | Example |
324
+ | --- | --- | --- |
325
+ | `* %% [title]` | Cell boundary. Each marker gets a **▶ Run Cell** code-lens; "Run Cell" submits the lines between this marker and the next one. Compatible with the Jupyter-style cell convention used by `kylebutts/vscode-stata`. | `* %% 02 model fit` |
326
+ | `**# title` … `**###### title` | Section heading, 1–6 levels deep. Each heading gets a **▶ Run Section** code-lens and contributes to the Outline view. "Run Section" submits the heading through the next equal- or higher-level heading, matching the hierarchical execution model from `ZihaoVistonWang.stata-all-in-one`. | `**## DiD specification` |
327
+
328
+ `program define … end` blocks are also surfaced in the Outline, nested under whichever section contains them.
269
329
 
270
330
  The extension still requires the MCP extra on your system Python (`pip install "stata-code[mcp]"`), so that `stata-code-mcp` resolves on `PATH` and can import the MCP SDK. Stata 17+ and a valid Stata license are required as for any other frontend.
271
331
 
@@ -358,7 +418,7 @@ stata_code/
358
418
  - MCP server: 15 tools, including notebook navigation / search / atomic edits and the run-bundle index (`list_runs`)
359
419
  - Jupyter kernel: rewired to the v1.0 pipeline, kernel logos bundled
360
420
  - Matrix size cap + `get_matrix(ref)` for large matrices (>10k cells)
361
- - Cooperative cancellation: `cancel(session_id)` / MCP `cancel_session`
421
+ - Subprocess-backed hard timeout and cancellation for the public Python API and MCP server: `timeout_ms`, `cancel(session_id)`, and MCP `cancel_session`
362
422
  - Per-cell repair loop on `.ipynb` via `notebook_outline` / `notebook_get_cell` / `notebook_edit_cell` with optimistic-concurrency `expected_source` guards and `origin_cell_id` echo on `RunResult`
363
423
  - Persistent run bundles + `list_runs` query over `manifest.json` files (filter by cell / origin / session / since / ok)
364
424
  - JSON Schema artifact auto-generated from `schema.py`: [`schema/run_result.schema.json`](schema/run_result.schema.json)
@@ -368,8 +428,8 @@ stata_code/
368
428
  ### Next Up
369
429
 
370
430
  - Console fallback for Stata 11–16, re-implemented against the v1.0 schema
371
- - Hard timeout / mid-Stata interrupt; design and tradeoffs in [`docs/design/hard_timeout.md`](docs/design/hard_timeout.md)
372
- - Extra VS Code polish (esbuild bundle, lighter VSIX, command palette UX)
431
+ - Decide whether to move the Jupyter kernel from the direct in-process runner to the subprocess pool, or keep documenting the current interactivity-first tradeoff
432
+ - Extra VS Code polish: extension-host end-to-end tests, first-run diagnostics, and command palette UX
373
433
  - **v1.0** — Stable schema, broader Stata edition coverage
374
434
 
375
435
  See [SCHEMA.md §7](SCHEMA.md) for explicitly out-of-scope items.
@@ -380,9 +440,9 @@ See [SCHEMA.md §7](SCHEMA.md) for explicitly out-of-scope items.
380
440
 
381
441
  ```bash
382
442
  pip install -e ".[dev,mcp,kernel]"
383
- pytest # full suite (310 tests)
443
+ pytest # full suite, including Stata tests when Stata is available
384
444
  pytest -m "not stata_required" # CI subset; no Stata needed
385
- pytest -m "stata_required" -v # Stata-only integration tests
445
+ pytest -m "stata_required" -v # real-Stata integration tests only
386
446
  ```
387
447
 
388
448
  The `stata_required` marker tags the real-Stata integration tests. CI uses `pytest -m "not stata_required"` so it does not collect them. Locally without Stata, those tests skip cleanly with the `"pystata / Stata 17+ not available"` message.
@@ -430,7 +430,7 @@ Suggestions are best-effort; agents should treat them as hints, not directives.
430
430
  | `stata_limit` | 901, 902, 903 | Edition / matsize / similar Stata-imposed caps. Distinct from OS OOM. Suggestion: `set maxvar` or upgrade edition. |
431
431
  | `out_of_memory` | 480, 909 | OS-level memory exhaustion. |
432
432
  | `interrupt` | 1 | User Break / Ctrl-C from a frontend. |
433
- | `cancelled` | (synthetic `rc: -3`) | Cooperative cancellation: a prior `cancel(session_id)` short-circuited this run before Stata received the code. |
433
+ | `cancelled` | (synthetic `rc: -3`) | Cancellation was requested. Subprocess-backed producers may terminate an in-flight worker; the direct in-process runner only short-circuits before Stata receives code. |
434
434
  | `timeout` | (synthetic `rc: -2`) | Adapter-imposed time limit exceeded. |
435
435
  | `adapter_crash` | (synthetic `rc: -1`) | Producer-side failure (pystata exception, IPC death). |
436
436
  | `unknown` | any unmapped rc | Catch-all. Agents fall back to `message`. We aim to shrink this over time. |
@@ -1,14 +1,16 @@
1
1
  # Design note: hard timeout / mid-Stata interrupt
2
2
 
3
- > Status: **deferred** not implemented in v0.2/early-v0.3. This document
4
- > exists so the next person picking it up has the constraints written
5
- > down. After this lands, `cooperative cancellation` (shipping in this
6
- > push) covers the "abort the next call" case; the gap is killing a
7
- > Stata command that is already executing.
3
+ > Status: **partially shipped**. The public Python API and MCP server now use
4
+ > the subprocess-worker architecture described below (`stata_code.core._pool`),
5
+ > so `timeout_ms` and in-flight `cancel_session` terminate the worker and return
6
+ > structured `timeout` / `cancelled` results. The remaining gap is the direct
7
+ > in-process runner (`stata_code.core.runner.execute`) and the Jupyter kernel,
8
+ > which still use `pystata` in-process for interactivity. This note is retained
9
+ > as the rationale for that boundary.
8
10
 
9
11
  ## The constraint
10
12
 
11
- `stata_code` runs Stata via **pystata, in-process**. The runner imports
13
+ The direct runner runs Stata via **pystata, in-process**. It imports
12
14
  `pystata.config`, calls `pystata.config.init(edition)`, then submits
13
15
  code through `stata.run(code)`. That call is a synchronous, blocking C
14
16
  call into Stata's runtime. From Python's perspective, the interpreter
@@ -26,7 +28,7 @@ There is **no public pystata API** to:
26
28
  is process-wide; a second `init()` is rejected. Frames give us
27
29
  data isolation, not control isolation.
28
30
 
29
- So `timeout_ms` cannot be enforced in the current architecture.
31
+ So `timeout_ms` cannot be enforced in the direct in-process runner.
30
32
  `cancel(session_id)` (cooperative) catches the case where the agent
31
33
  hasn't yet submitted the next command, but if the agent already issued
32
34
  `bootstrap, reps(10000): regress …` and is mid-flight, nothing on the
@@ -43,7 +45,7 @@ and a synthetic `rc: -2` for exactly this case.
43
45
 
44
46
  ## Options considered
45
47
 
46
- ### Option A — Subprocess pystata worker (recommended)
48
+ ### Option A — Subprocess pystata worker (shipped for public API / MCP)
47
49
 
48
50
  Move pystata out of the main Python process. One persistent worker
49
51
  subprocess per session, with a small JSON-over-stdio protocol:
@@ -140,22 +142,13 @@ When Option A is approved, the rough work breakdown is:
140
142
 
141
143
  Total: ~8–10 days of focused work.
142
144
 
143
- ## Why not now
145
+ ## Why not in the direct runner / kernel yet
144
146
 
145
- This v0.3 push deliberately scoped to changes that do not require
146
- restructuring `_runtime.py`. Shipping Option A as a side-effect of a
147
- multi-feature push would leave it under-tested and ill-thought-through.
148
- It deserves its own milestone where (a) the design above is reviewed
149
- and confirmed, (b) the work is tracked across at least one full
150
- development week, and (c) any breaking change to the runtime model
151
- is announced ahead of time so downstream callers can plan.
152
-
153
- In the meantime the ground we did cover is real:
154
-
155
- - Cooperative cancellation (`cancel(session_id)`) — in this push.
156
- - Token economy preserved end-to-end (refs, not bytes) — in this push.
157
- - VSCode webview that exercises the same MCP surface a hard-timeout
158
- agent would — in this push.
159
-
160
- The hardest piece is still ahead. This document exists so the next
161
- person picking it up doesn't have to rediscover the constraints.
147
+ The subprocess pool is now the default for the package-level API and MCP
148
+ server. The lower-level `core.runner.execute()` remains available as an
149
+ explicit in-process escape hatch, and the Jupyter kernel still calls it so
150
+ notebook cells can use inline logs and graphs without paying the worker
151
+ round-trip or changing notebook semantics. Moving the kernel onto the pool is
152
+ possible, but should be treated as a separate UX decision: hard interruption
153
+ would improve resilience, while worker death would discard in-memory Stata
154
+ state after timeouts or cancels.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "stata-code"
7
- version = "0.6.3"
7
+ version = "0.6.5"
8
8
  description = "Agent-native Stata bridge — one core, multiple frontends (MCP, Jupyter, VSCode)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -174,7 +174,7 @@ def is_available() -> bool:
174
174
  return True
175
175
 
176
176
 
177
- __version__ = "0.6.3"
177
+ __version__ = "0.6.5"
178
178
 
179
179
  __all__ = [
180
180
  # Primary entry points