stata-code 0.6.3__tar.gz → 0.6.4__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.4}/CHANGELOG.md +45 -0
  2. {stata_code-0.6.3 → stata_code-0.6.4}/PKG-INFO +60 -13
  3. {stata_code-0.6.3 → stata_code-0.6.4}/PUBLISHING.md +45 -22
  4. {stata_code-0.6.3 → stata_code-0.6.4}/README.md +59 -12
  5. {stata_code-0.6.3 → stata_code-0.6.4}/SCHEMA.md +1 -1
  6. {stata_code-0.6.3 → stata_code-0.6.4}/docs/design/hard_timeout.md +19 -26
  7. {stata_code-0.6.3 → stata_code-0.6.4}/pyproject.toml +1 -1
  8. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/__init__.py +1 -1
  9. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/core/_pool.py +72 -14
  10. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/core/_runtime.py +105 -12
  11. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/core/runner.py +48 -40
  12. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/mcp/server.py +2 -2
  13. {stata_code-0.6.3 → stata_code-0.6.4}/tests/test_pool.py +62 -0
  14. stata_code-0.6.4/tests/test_runtime_discovery.py +232 -0
  15. stata_code-0.6.3/tests/test_runtime_discovery.py +0 -81
  16. {stata_code-0.6.3 → stata_code-0.6.4}/.gitignore +0 -0
  17. {stata_code-0.6.3 → stata_code-0.6.4}/LICENSE +0 -0
  18. {stata_code-0.6.3 → stata_code-0.6.4}/LICENSE-POLICY.md +0 -0
  19. {stata_code-0.6.3 → stata_code-0.6.4}/examples/01-basic-regression.md +0 -0
  20. {stata_code-0.6.3 → stata_code-0.6.4}/examples/02-did-card-krueger.md +0 -0
  21. {stata_code-0.6.3 → stata_code-0.6.4}/examples/03-graphs.md +0 -0
  22. {stata_code-0.6.3 → stata_code-0.6.4}/examples/04-multi-session.md +0 -0
  23. {stata_code-0.6.3 → stata_code-0.6.4}/examples/05-large-matrix.md +0 -0
  24. {stata_code-0.6.3 → stata_code-0.6.4}/examples/README.md +0 -0
  25. {stata_code-0.6.3 → stata_code-0.6.4}/schema/run_result.schema.json +0 -0
  26. {stata_code-0.6.3 → stata_code-0.6.4}/scripts/check_versions.py +0 -0
  27. {stata_code-0.6.3 → stata_code-0.6.4}/scripts/export_schema.py +0 -0
  28. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/core/__init__.py +0 -0
  29. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/core/_refs.py +0 -0
  30. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/core/errors.py +0 -0
  31. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/core/log_artifacts.py +0 -0
  32. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/core/notebook.py +0 -0
  33. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/core/run_index.py +0 -0
  34. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/core/schema.py +0 -0
  35. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/kernel/__init__.py +0 -0
  36. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/kernel/__main__.py +0 -0
  37. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/kernel/assets/logo-32x32.png +0 -0
  38. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/kernel/assets/logo-64x64.png +0 -0
  39. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/kernel/assets/logo-svg.svg +0 -0
  40. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/kernel/kernel.py +0 -0
  41. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/mcp/__init__.py +0 -0
  42. {stata_code-0.6.3 → stata_code-0.6.4}/stata_code/mcp/__main__.py +0 -0
  43. {stata_code-0.6.3 → stata_code-0.6.4}/tests/__init__.py +0 -0
  44. {stata_code-0.6.3 → stata_code-0.6.4}/tests/conftest.py +0 -0
  45. {stata_code-0.6.3 → stata_code-0.6.4}/tests/fixtures/.gitkeep +0 -0
  46. {stata_code-0.6.3 → stata_code-0.6.4}/tests/test_cancel.py +0 -0
  47. {stata_code-0.6.3 → stata_code-0.6.4}/tests/test_errors.py +0 -0
  48. {stata_code-0.6.3 → stata_code-0.6.4}/tests/test_kernel.py +0 -0
  49. {stata_code-0.6.3 → stata_code-0.6.4}/tests/test_log_artifacts.py +0 -0
  50. {stata_code-0.6.3 → stata_code-0.6.4}/tests/test_mcp.py +0 -0
  51. {stata_code-0.6.3 → stata_code-0.6.4}/tests/test_mcp_stdio.py +0 -0
  52. {stata_code-0.6.3 → stata_code-0.6.4}/tests/test_notebook.py +0 -0
  53. {stata_code-0.6.3 → stata_code-0.6.4}/tests/test_notebook_phase2.py +0 -0
  54. {stata_code-0.6.3 → stata_code-0.6.4}/tests/test_public_api.py +0 -0
  55. {stata_code-0.6.3 → stata_code-0.6.4}/tests/test_run_index.py +0 -0
  56. {stata_code-0.6.3 → stata_code-0.6.4}/tests/test_runner.py +0 -0
  57. {stata_code-0.6.3 → stata_code-0.6.4}/tests/test_schema.py +0 -0
  58. {stata_code-0.6.3 → stata_code-0.6.4}/tests/test_schema_artifact.py +0 -0
@@ -6,6 +6,51 @@ to semver-major.minor for the result schema (see `SCHEMA.md` §6).
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## 0.6.4 — 2026-05-21
10
+
11
+ ### Added
12
+
13
+ - **Claude Code plugin marketplace manifest.** `.claude-plugin/marketplace.json`
14
+ + `.claude-plugin/plugin.json` expose the repo as a single-plugin
15
+ marketplace, so users can install everything (MCP server config + agent
16
+ skill) with `claude plugin marketplace add brycewang-stanford/stata-code`
17
+ followed by `claude plugin install stata-code`.
18
+ - **`stata-code` agent skill.** `skills/stata-code/SKILL.md` teaches Claude
19
+ the v1.0 RunResult schema, the 15 MCP tools, the token-economy defaults,
20
+ the 32-kind error taxonomy, and the diagnose-only vs. fix-and-rerun
21
+ workflows. The plugin manifest auto-installs it alongside the MCP
22
+ server.
23
+ - **VS Code install-hint probe.** On activation the extension now resolves
24
+ the configured `stata-code-mcp` candidate list against `PATH` and any
25
+ workspace `.venv` / `venv`; if nothing matches, a one-time notification
26
+ offers to copy the `pip install "stata-code[mcp]"` command or open the
27
+ install docs. "Don't show again" pins the dismissal to the installed
28
+ extension version. Backed by a new pure module `serverProbe.ts` with
29
+ unit tests.
30
+ - **README multi-client section.** Cursor, Claude Desktop, Cline,
31
+ Continue, Windsurf, and Antigravity now have their config-file paths
32
+ spelled out next to the shared `stata-code-mcp` JSON snippet, plus
33
+ guidance for project-venv absolute paths and `uvx` setups.
34
+ - **README cell + section reference.** Documents that the VS Code
35
+ extension recognizes both `* %%` Jupyter-style cell markers and
36
+ `**#` … `**######` six-level section headings in `.do` files, and how
37
+ each interacts with the code-lens and Outline view.
38
+ - **Open VSX publish step.** `vscode-release.yml` now publishes the VSIX
39
+ to Open VSX on every `vscode-v*` tag. The step is gated on
40
+ `OVSX_PAT` being set at runtime and runs with `continue-on-error: true`,
41
+ so a missing or expired token never blocks the primary VS Code
42
+ Marketplace publish.
43
+
44
+ ### Fixed
45
+
46
+ - **Subprocess worker JSON protocol hardening.** Worker processes now keep
47
+ their private JSON protocol fds separate from real stdin/stdout and
48
+ redirect the real fd 0/1 pair to `os.devnull` before importing the
49
+ runner. The parent reader also ignores blank protocol noise while still
50
+ failing on non-empty non-JSON output. This prevents a pystata/Stata
51
+ initialization newline from surfacing as
52
+ `adapter_crash: worker emitted non-JSON: '\n'`.
53
+
9
54
  ## 0.6.3 — 2026-05-10
10
55
 
11
56
  ### 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.4
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,26 @@ 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
+
232
266
  The MCP server registers 15 tools:
233
267
 
234
268
  | Tool | Purpose |
@@ -239,7 +273,7 @@ The MCP server registers 15 tools:
239
273
  | `get_graph` | Fetch graph bytes behind a `graph://` ref (`ImageContent`) |
240
274
  | `get_matrix` | Fetch matrix payloads behind a `matrix://` ref |
241
275
  | `list_sessions` | Enumerate live sessions |
242
- | `cancel_session` | Cooperatively cancel the next `stata_run` for a session |
276
+ | `cancel_session` | Cancel a session; the subprocess-backed path terminates in-flight runs and short-circuits pending ones |
243
277
  | `reset_session` | Drop a session's data |
244
278
  | `notebook_outline` | Compact per-cell index of a `.ipynb` (cell_id, type, preview) |
245
279
  | `notebook_get_cell` | One cell's full source plus a token-economic outputs summary |
@@ -304,7 +338,20 @@ The companion extension is on the Marketplace as [`brycewang-stanford.stata-code
304
338
  code --install-extension brycewang-stanford.stata-code-vscode
305
339
  ```
306
340
 
307
- Or open the **Extensions** sidebar in VS Code and search `stata-code`.
341
+ 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.
342
+
343
+ 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.
344
+
345
+ #### Cell and section conventions
346
+
347
+ The extension recognizes two complementary structural markers inside `.do` files. Either can be mixed in the same file; they do not conflict.
348
+
349
+ | Marker | Purpose | Example |
350
+ | --- | --- | --- |
351
+ | `* %% [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` |
352
+ | `**# 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` |
353
+
354
+ `program define … end` blocks are also surfaced in the Outline, nested under whichever section contains them.
308
355
 
309
356
  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
357
 
@@ -397,7 +444,7 @@ stata_code/
397
444
  - MCP server: 15 tools, including notebook navigation / search / atomic edits and the run-bundle index (`list_runs`)
398
445
  - Jupyter kernel: rewired to the v1.0 pipeline, kernel logos bundled
399
446
  - Matrix size cap + `get_matrix(ref)` for large matrices (>10k cells)
400
- - Cooperative cancellation: `cancel(session_id)` / MCP `cancel_session`
447
+ - Subprocess-backed hard timeout and cancellation for the public Python API and MCP server: `timeout_ms`, `cancel(session_id)`, and MCP `cancel_session`
401
448
  - 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
449
  - Persistent run bundles + `list_runs` query over `manifest.json` files (filter by cell / origin / session / since / ok)
403
450
  - JSON Schema artifact auto-generated from `schema.py`: [`schema/run_result.schema.json`](schema/run_result.schema.json)
@@ -407,8 +454,8 @@ stata_code/
407
454
  ### Next Up
408
455
 
409
456
  - 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)
457
+ - 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
458
+ - Extra VS Code polish: extension-host end-to-end tests, first-run diagnostics, and command palette UX
412
459
  - **v1.0** — Stable schema, broader Stata edition coverage
413
460
 
414
461
  See [SCHEMA.md §7](SCHEMA.md) for explicitly out-of-scope items.
@@ -419,9 +466,9 @@ See [SCHEMA.md §7](SCHEMA.md) for explicitly out-of-scope items.
419
466
 
420
467
  ```bash
421
468
  pip install -e ".[dev,mcp,kernel]"
422
- pytest # full suite (310 tests)
469
+ pytest # full suite, including Stata tests when Stata is available
423
470
  pytest -m "not stata_required" # CI subset; no Stata needed
424
- pytest -m "stata_required" -v # Stata-only integration tests
471
+ pytest -m "stata_required" -v # real-Stata integration tests only
425
472
  ```
426
473
 
427
474
  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,26 @@ 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
+
193
227
  The MCP server registers 15 tools:
194
228
 
195
229
  | Tool | Purpose |
@@ -200,7 +234,7 @@ The MCP server registers 15 tools:
200
234
  | `get_graph` | Fetch graph bytes behind a `graph://` ref (`ImageContent`) |
201
235
  | `get_matrix` | Fetch matrix payloads behind a `matrix://` ref |
202
236
  | `list_sessions` | Enumerate live sessions |
203
- | `cancel_session` | Cooperatively cancel the next `stata_run` for a session |
237
+ | `cancel_session` | Cancel a session; the subprocess-backed path terminates in-flight runs and short-circuits pending ones |
204
238
  | `reset_session` | Drop a session's data |
205
239
  | `notebook_outline` | Compact per-cell index of a `.ipynb` (cell_id, type, preview) |
206
240
  | `notebook_get_cell` | One cell's full source plus a token-economic outputs summary |
@@ -265,7 +299,20 @@ The companion extension is on the Marketplace as [`brycewang-stanford.stata-code
265
299
  code --install-extension brycewang-stanford.stata-code-vscode
266
300
  ```
267
301
 
268
- Or open the **Extensions** sidebar in VS Code and search `stata-code`.
302
+ 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.
303
+
304
+ 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.
305
+
306
+ #### Cell and section conventions
307
+
308
+ The extension recognizes two complementary structural markers inside `.do` files. Either can be mixed in the same file; they do not conflict.
309
+
310
+ | Marker | Purpose | Example |
311
+ | --- | --- | --- |
312
+ | `* %% [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` |
313
+ | `**# 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` |
314
+
315
+ `program define … end` blocks are also surfaced in the Outline, nested under whichever section contains them.
269
316
 
270
317
  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
318
 
@@ -358,7 +405,7 @@ stata_code/
358
405
  - MCP server: 15 tools, including notebook navigation / search / atomic edits and the run-bundle index (`list_runs`)
359
406
  - Jupyter kernel: rewired to the v1.0 pipeline, kernel logos bundled
360
407
  - Matrix size cap + `get_matrix(ref)` for large matrices (>10k cells)
361
- - Cooperative cancellation: `cancel(session_id)` / MCP `cancel_session`
408
+ - Subprocess-backed hard timeout and cancellation for the public Python API and MCP server: `timeout_ms`, `cancel(session_id)`, and MCP `cancel_session`
362
409
  - 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
410
  - Persistent run bundles + `list_runs` query over `manifest.json` files (filter by cell / origin / session / since / ok)
364
411
  - JSON Schema artifact auto-generated from `schema.py`: [`schema/run_result.schema.json`](schema/run_result.schema.json)
@@ -368,8 +415,8 @@ stata_code/
368
415
  ### Next Up
369
416
 
370
417
  - 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)
418
+ - 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
419
+ - Extra VS Code polish: extension-host end-to-end tests, first-run diagnostics, and command palette UX
373
420
  - **v1.0** — Stable schema, broader Stata edition coverage
374
421
 
375
422
  See [SCHEMA.md §7](SCHEMA.md) for explicitly out-of-scope items.
@@ -380,9 +427,9 @@ See [SCHEMA.md §7](SCHEMA.md) for explicitly out-of-scope items.
380
427
 
381
428
  ```bash
382
429
  pip install -e ".[dev,mcp,kernel]"
383
- pytest # full suite (310 tests)
430
+ pytest # full suite, including Stata tests when Stata is available
384
431
  pytest -m "not stata_required" # CI subset; no Stata needed
385
- pytest -m "stata_required" -v # Stata-only integration tests
432
+ pytest -m "stata_required" -v # real-Stata integration tests only
386
433
  ```
387
434
 
388
435
  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.4"
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.4"
178
178
 
179
179
  __all__ = [
180
180
  # Primary entry points