stata-code 0.6.2__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 (59) hide show
  1. {stata_code-0.6.2 → stata_code-0.6.4}/CHANGELOG.md +186 -0
  2. {stata_code-0.6.2 → stata_code-0.6.4}/PKG-INFO +68 -15
  3. {stata_code-0.6.2 → stata_code-0.6.4}/PUBLISHING.md +45 -22
  4. {stata_code-0.6.2 → stata_code-0.6.4}/README.md +66 -14
  5. {stata_code-0.6.2 → stata_code-0.6.4}/SCHEMA.md +17 -20
  6. {stata_code-0.6.2 → stata_code-0.6.4}/docs/design/hard_timeout.md +19 -26
  7. {stata_code-0.6.2 → stata_code-0.6.4}/pyproject.toml +2 -1
  8. {stata_code-0.6.2 → stata_code-0.6.4}/schema/run_result.schema.json +1 -1
  9. stata_code-0.6.4/scripts/check_versions.py +104 -0
  10. {stata_code-0.6.2 → stata_code-0.6.4}/scripts/export_schema.py +4 -1
  11. stata_code-0.6.4/stata_code/__init__.py +221 -0
  12. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/core/_pool.py +269 -50
  13. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/core/_runtime.py +118 -16
  14. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/core/errors.py +17 -8
  15. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/core/log_artifacts.py +20 -2
  16. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/core/notebook.py +30 -11
  17. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/core/run_index.py +27 -3
  18. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/core/runner.py +111 -58
  19. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/kernel/kernel.py +36 -11
  20. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/mcp/server.py +181 -77
  21. stata_code-0.6.4/tests/conftest.py +51 -0
  22. {stata_code-0.6.2 → stata_code-0.6.4}/tests/test_cancel.py +3 -0
  23. {stata_code-0.6.2 → stata_code-0.6.4}/tests/test_mcp.py +208 -22
  24. stata_code-0.6.4/tests/test_mcp_stdio.py +163 -0
  25. {stata_code-0.6.2 → stata_code-0.6.4}/tests/test_pool.py +109 -0
  26. stata_code-0.6.4/tests/test_public_api.py +71 -0
  27. stata_code-0.6.4/tests/test_runtime_discovery.py +232 -0
  28. stata_code-0.6.2/stata_code/__init__.py +0 -102
  29. stata_code-0.6.2/tests/test_runtime_discovery.py +0 -81
  30. {stata_code-0.6.2 → stata_code-0.6.4}/.gitignore +0 -0
  31. {stata_code-0.6.2 → stata_code-0.6.4}/LICENSE +0 -0
  32. {stata_code-0.6.2 → stata_code-0.6.4}/LICENSE-POLICY.md +0 -0
  33. {stata_code-0.6.2 → stata_code-0.6.4}/examples/01-basic-regression.md +0 -0
  34. {stata_code-0.6.2 → stata_code-0.6.4}/examples/02-did-card-krueger.md +0 -0
  35. {stata_code-0.6.2 → stata_code-0.6.4}/examples/03-graphs.md +0 -0
  36. {stata_code-0.6.2 → stata_code-0.6.4}/examples/04-multi-session.md +0 -0
  37. {stata_code-0.6.2 → stata_code-0.6.4}/examples/05-large-matrix.md +0 -0
  38. {stata_code-0.6.2 → stata_code-0.6.4}/examples/README.md +0 -0
  39. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/core/__init__.py +0 -0
  40. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/core/_refs.py +0 -0
  41. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/core/schema.py +0 -0
  42. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/kernel/__init__.py +0 -0
  43. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/kernel/__main__.py +0 -0
  44. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/kernel/assets/logo-32x32.png +0 -0
  45. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/kernel/assets/logo-64x64.png +0 -0
  46. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/kernel/assets/logo-svg.svg +0 -0
  47. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/mcp/__init__.py +0 -0
  48. {stata_code-0.6.2 → stata_code-0.6.4}/stata_code/mcp/__main__.py +0 -0
  49. {stata_code-0.6.2 → stata_code-0.6.4}/tests/__init__.py +0 -0
  50. {stata_code-0.6.2 → stata_code-0.6.4}/tests/fixtures/.gitkeep +0 -0
  51. {stata_code-0.6.2 → stata_code-0.6.4}/tests/test_errors.py +0 -0
  52. {stata_code-0.6.2 → stata_code-0.6.4}/tests/test_kernel.py +0 -0
  53. {stata_code-0.6.2 → stata_code-0.6.4}/tests/test_log_artifacts.py +0 -0
  54. {stata_code-0.6.2 → stata_code-0.6.4}/tests/test_notebook.py +0 -0
  55. {stata_code-0.6.2 → stata_code-0.6.4}/tests/test_notebook_phase2.py +0 -0
  56. {stata_code-0.6.2 → stata_code-0.6.4}/tests/test_run_index.py +0 -0
  57. {stata_code-0.6.2 → stata_code-0.6.4}/tests/test_runner.py +0 -0
  58. {stata_code-0.6.2 → stata_code-0.6.4}/tests/test_schema.py +0 -0
  59. {stata_code-0.6.2 → stata_code-0.6.4}/tests/test_schema_artifact.py +0 -0
@@ -6,6 +6,192 @@ 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
+
54
+ ## 0.6.3 — 2026-05-10
55
+
56
+ ### Added
57
+
58
+ - **`RefNotFound` exception** — `get_log` / `get_graph` / `get_matrix` now
59
+ raise a typed `RefNotFound` (subclass of `KeyError`) carrying the bad
60
+ ref and a stable `kind` token (`unknown_log_ref` / `unknown_graph_ref` /
61
+ `unknown_matrix_ref`). MCP dispatch maps it to a typed error response
62
+ without string-parsing.
63
+ - **`NotebookError.kind` / `RunIndexError.kind`** — both classes now
64
+ expose a `kind` property so callers don't have to slice the message
65
+ prefix manually.
66
+ - **`SessionPool.list_session_info_detailed`** — partial-failure-aware
67
+ variant of `list_session_info()` that surfaces per-worker `warnings`
68
+ alongside the aggregated `sessions` list. The MCP `list_sessions`
69
+ tool's output schema now includes the optional `warnings` field.
70
+ - **`list_runs.requested_limit`** — echoes the original requested limit
71
+ when the server clamps it to `_LIMIT_MAX`, so a caller asking for
72
+ `limit=1000` can distinguish "scan was capped at 500 rows" from "the
73
+ manifest dir really has more than 500 rows".
74
+ - **Server capabilities resource now includes prompts.** Reading
75
+ `stata://server/capabilities` returns tools, resource templates, AND
76
+ prompts in a single document — clients no longer need a follow-up
77
+ `list_prompts` round-trip just to enumerate the full surface.
78
+
79
+ ### Changed
80
+
81
+ - **Test hygiene** — a `tests/conftest.py` snapshot/restores `_refs._store`
82
+ per test and shuts the default subprocess pool down at session end.
83
+ Closes a class of intermittent cross-test failures caused by ref-store
84
+ pollution from earlier MCP / pool tests.
85
+ - **Subprocess cancellation race** — `SessionPool.execute()` now
86
+ re-checks `_cancel_pending` after `_get_or_spawn` so a `request_cancel`
87
+ that lands during worker spawn fires on the in-flight call instead of
88
+ the next one.
89
+ - **`stata_info` edition casing is now consistent** — the top-level
90
+ `edition` field mirrors `stata.edition` (the enum value, e.g. `MP`)
91
+ verbatim. Previously the pool path lowercased it to `mp` while the
92
+ in-process path emitted `MP`, so the same payload could disagree with
93
+ itself.
94
+ - **All MCP tool input schemas now declare `additionalProperties: false`.**
95
+ Typos like `originPath` (camelCase) or misspelled `log_lin_head` are
96
+ now rejected up-front with a typed validation error instead of silently
97
+ producing a wrong run.
98
+ - **`cancel_session` and `reset_session` output schemas split.** Each
99
+ tool now advertises only the fields it actually returns; the previous
100
+ shared schema overpromised on one side and underpromised on the other.
101
+ - **`list_resources` caps ref-backed entries at 256.** Long-lived
102
+ servers no longer return thousands of stale ref resources from
103
+ forgotten sessions — the most recently used 256 win.
104
+ - **Pystata edition init now preserves the full error trail.** When all
105
+ three editions (MP / SE / BE) fail, the `PystataNotAvailable` message
106
+ lists each attempt's error rather than collapsing to the last one.
107
+
108
+ ### Fixed
109
+
110
+ - **Jupyter kernel `implementation_version`** is now derived from
111
+ `stata_code.__version__` rather than a separate `0.2.0` literal.
112
+ - **Jupyter kernel mypy `Invalid base class`** error — the dynamic
113
+ `Kernel if _HAS_IPYKERNEL else object` base class confused mypy.
114
+ Hidden behind a `TYPE_CHECKING` gate now; runtime behavior unchanged.
115
+ - **`do_execute` / `do_inspect` signatures** are now LSP-compatible
116
+ with the latest `ipykernel.kernelbase.Kernel` (accepts `cell_meta`,
117
+ `cell_id`, `omit_sections`).
118
+ - **Schema `$id` URL** corrected from `stata_code` to `stata-code`
119
+ (matches the actual GitHub repository name).
120
+ - **VS Code MCP handshake** version is now read from `package.json`
121
+ via `resolveJsonModule`, removing the former fifth sync site.
122
+ `check_versions.py` still validates the literal form
123
+ when present, so reverting to a hardcoded string fails CI.
124
+ - **Release tag glob tightened** — `v[0-9]*.[0-9]*.[0-9]*` instead of
125
+ `v[0-9]*` so stray tags without semver-style dot separators no longer
126
+ trigger the release workflow.
127
+ - **Release pipeline now gates on ruff** so a direct push to `main`
128
+ cannot ship un-linted code.
129
+ - **Mypy CI now covers `stata_code/mcp` and `stata_code/kernel`** in
130
+ addition to `core`, with the optional `mcp` / `kernel` extras
131
+ installed so `Server`, `Tool`, and `Kernel` symbols resolve.
132
+ - **`COMMON_STATA_COMMANDS` deduplicated** — dropped the short forms
133
+ (`cap` / `qui` / `noi` / `mat` / `di`) that were producing
134
+ near-duplicate "did you mean" hits from `difflib`. The long forms
135
+ cover the same fuzzy-match neighbourhood.
136
+ - **`_unique_dir` / `_unique_file`** fall back to a UUID suffix after
137
+ 998 collisions instead of raising `FileExistsError`. The original
138
+ behaviour blocked the run on conditions that almost always indicate a
139
+ filesystem issue rather than a name clash.
140
+ - **Dead code removed** — `_info_payload` (orphaned helper) in the MCP
141
+ server, `_truncate` in `notebook.py`, and the unused `index` / `source`
142
+ parameters on `_ensure_native_id`.
143
+
144
+ ## 0.6.2 — 2026-05-08
145
+
146
+ Aggregated from the prior `Unreleased` section; covers 0.6.1 and 0.6.2.
147
+
148
+ ### Added
149
+
150
+ - **Release version guard.** `scripts/check_versions.py` and the CI /
151
+ release workflows now verify that the Python package, MCP server, VS Code
152
+ package, VS Code MCP handshake, and release tag all use the same version.
153
+ - **VS Code MCP launch tests.** The extension's server-launch candidate
154
+ builder is now a pure tested module covering local `.venv` / `venv`
155
+ discovery, configured Python interpreters, inline command parsing, and
156
+ environment construction.
157
+ - **VS Code packaging smoke test.** The default CI now builds a VSIX artifact
158
+ with the same local `vsce` package command used by release packaging, so
159
+ package-manifest and `.vscodeignore` mistakes fail before release day.
160
+ - **VS Code extension bundling.** The extension now bundles its TypeScript
161
+ entrypoint with `esbuild`, excluding `node_modules` and test/build support
162
+ files from the shipped VSIX.
163
+ - **VS Code bundled dependency notices.** `THIRD_PARTY_NOTICES.md` now lists
164
+ the npm packages included in the bundled extension output and their license
165
+ terms.
166
+ - **PyPI publish verification.** The release workflow now polls PyPI's
167
+ per-version JSON endpoint after the official publish job, so trusted
168
+ publisher failures surface as a failed release run instead of being hidden
169
+ by `continue-on-error`.
170
+
171
+ ### Changed
172
+
173
+ - **Package-level Python API is subprocess-backed.** `stata_code.run()`,
174
+ `execute()`, and `is_available()` now go through the same worker pool as
175
+ the MCP server, preserving hard timeout behavior and avoiding caller-process
176
+ stdout redirection by `pystata`.
177
+ - **CI treats core type errors as blocking.** `mypy stata_code/core` is now a
178
+ hard failure, and the default test workflow also compiles and tests the VS
179
+ Code extension.
180
+ - **VS Code npm installs are locked.** The extension now ships
181
+ `package-lock.json`, and CI / release workflows use `npm ci` for reproducible
182
+ dependency installs.
183
+ - **VS Code bundle target matches the extension host floor.** The esbuild
184
+ target is `node18`, keeping the published bundle aligned with the current
185
+ `engines.vscode` lower bound.
186
+ - **Python 3.13 is in the support matrix.** CI now runs the no-Stata test
187
+ suite on Python 3.13, and package metadata advertises the 3.13 classifier.
188
+
189
+ ### Fixed
190
+
191
+ - **Cancelled in-flight runs report incomplete logs.** If a live worker is
192
+ killed by `cancel_session`, the synthetic cancelled result now marks
193
+ `log.complete=false`, matching timeout and adapter-crash behavior.
194
+
9
195
  ## [0.6.0] — 2026-05-08
10
196
 
11
197
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stata-code
3
- Version: 0.6.2
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
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3
18
18
  Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
21
22
  Classifier: Topic :: Scientific/Engineering :: Mathematics
22
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
24
  Requires-Python: >=3.10
@@ -83,7 +84,7 @@ Description-Content-Type: text/markdown
83
84
  └─────────────┘ └────────────┘ └─────────────────┘
84
85
  ```
85
86
 
86
- **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: 310 passing tests across schema, runner, MCP, kernel, notebook, and run-index 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**.
87
88
 
88
89
  Two workflows v0.6 explicitly supports for end users:
89
90
 
@@ -145,6 +146,10 @@ See [`examples/`](examples/) for end-to-end cookbook entries: basic regression,
145
146
 
146
147
  ### As a Python Library
147
148
 
149
+ The package-level `run()` / `execute()` API uses the same subprocess-backed
150
+ runner as the MCP server, so long calls honor `timeout_ms` and `pystata`
151
+ stdout redirection stays isolated from the caller process.
152
+
148
153
  ```python
149
154
  from stata_code import run
150
155
 
@@ -199,14 +204,34 @@ If you want the repair loop, say so explicitly. Otherwise, treat failed runs as
199
204
  If you prefer not to `pip install stata-code` globally, run it ephemerally through [`uv`](https://github.com/astral-sh/uv):
200
205
 
201
206
  ```bash
202
- 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
203
208
  ```
204
209
 
205
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.
206
211
 
207
- #### Manual JSON config (Cursor / Claude Desktop / fallback)
212
+ #### Claude Code via plugin marketplace
213
+
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)
208
224
 
209
- For clients without a `mcp add` CLI, edit the config file directly (`~/.claude/mcp.json`, Cursor settings, Claude Desktop `claude_desktop_config.json`, etc.):
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 |
210
235
 
211
236
  ```json
212
237
  {
@@ -218,12 +243,26 @@ For clients without a `mcp add` CLI, edit the config file directly (`~/.claude/m
218
243
  }
219
244
  ```
220
245
 
221
- 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:
222
247
 
223
248
  ```bash
224
249
  python -m stata_code.mcp
225
250
  ```
226
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
+
227
266
  The MCP server registers 15 tools:
228
267
 
229
268
  | Tool | Purpose |
@@ -234,7 +273,7 @@ The MCP server registers 15 tools:
234
273
  | `get_graph` | Fetch graph bytes behind a `graph://` ref (`ImageContent`) |
235
274
  | `get_matrix` | Fetch matrix payloads behind a `matrix://` ref |
236
275
  | `list_sessions` | Enumerate live sessions |
237
- | `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 |
238
277
  | `reset_session` | Drop a session's data |
239
278
  | `notebook_outline` | Compact per-cell index of a `.ipynb` (cell_id, type, preview) |
240
279
  | `notebook_get_cell` | One cell's full source plus a token-economic outputs summary |
@@ -299,7 +338,20 @@ The companion extension is on the Marketplace as [`brycewang-stanford.stata-code
299
338
  code --install-extension brycewang-stanford.stata-code-vscode
300
339
  ```
301
340
 
302
- 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.
303
355
 
304
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.
305
357
 
@@ -348,14 +400,15 @@ stata_code/
348
400
  │ ├── _refs.py # LRU ref store for log/graph/matrix payloads
349
401
  │ ├── schema.py # Pydantic v2 models for the v1.0 result schema
350
402
  │ ├── errors.py # rc → ErrorKind mapping + suggestion seeds
351
- └── runner.py # the one execute(); collects everything via sfi
403
+ ├── runner.py # in-process execute(); collects everything via sfi
404
+ │ └── _pool.py # subprocess workers for public API / MCP hard timeouts
352
405
  ├── mcp/
353
406
  │ └── server.py # MCP server (15 tools)
354
407
  └── kernel/
355
408
  └── kernel.py # Jupyter kernel
356
409
  ```
357
410
 
358
- `runner.py` is the only place that touches Stata. The Jupyter kernel and MCP server both import from it and only translate results into their own transports.
411
+ `runner.py` is the only place that directly talks to `pystata`. The public Python API and MCP server route calls through `_pool.py`, whose workers call `runner.execute()` in an isolated subprocess; the Jupyter kernel uses the in-process runner for notebook interactivity.
359
412
 
360
413
  ---
361
414
 
@@ -391,7 +444,7 @@ stata_code/
391
444
  - MCP server: 15 tools, including notebook navigation / search / atomic edits and the run-bundle index (`list_runs`)
392
445
  - Jupyter kernel: rewired to the v1.0 pipeline, kernel logos bundled
393
446
  - Matrix size cap + `get_matrix(ref)` for large matrices (>10k cells)
394
- - 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`
395
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`
396
449
  - Persistent run bundles + `list_runs` query over `manifest.json` files (filter by cell / origin / session / since / ok)
397
450
  - JSON Schema artifact auto-generated from `schema.py`: [`schema/run_result.schema.json`](schema/run_result.schema.json)
@@ -401,8 +454,8 @@ stata_code/
401
454
  ### Next Up
402
455
 
403
456
  - Console fallback for Stata 11–16, re-implemented against the v1.0 schema
404
- - Hard timeout / mid-Stata interrupt; design and tradeoffs in [`docs/design/hard_timeout.md`](docs/design/hard_timeout.md)
405
- - 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
406
459
  - **v1.0** — Stable schema, broader Stata edition coverage
407
460
 
408
461
  See [SCHEMA.md §7](SCHEMA.md) for explicitly out-of-scope items.
@@ -413,9 +466,9 @@ See [SCHEMA.md §7](SCHEMA.md) for explicitly out-of-scope items.
413
466
 
414
467
  ```bash
415
468
  pip install -e ".[dev,mcp,kernel]"
416
- pytest # full suite (310 tests)
469
+ pytest # full suite, including Stata tests when Stata is available
417
470
  pytest -m "not stata_required" # CI subset; no Stata needed
418
- pytest -m "stata_required" -v # Stata-only integration tests
471
+ pytest -m "stata_required" -v # real-Stata integration tests only
419
472
  ```
420
473
 
421
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: 310 passing tests across schema, runner, MCP, kernel, notebook, and run-index 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
 
@@ -107,6 +107,10 @@ See [`examples/`](examples/) for end-to-end cookbook entries: basic regression,
107
107
 
108
108
  ### As a Python Library
109
109
 
110
+ The package-level `run()` / `execute()` API uses the same subprocess-backed
111
+ runner as the MCP server, so long calls honor `timeout_ms` and `pystata`
112
+ stdout redirection stays isolated from the caller process.
113
+
110
114
  ```python
111
115
  from stata_code import run
112
116
 
@@ -161,14 +165,34 @@ If you want the repair loop, say so explicitly. Otherwise, treat failed runs as
161
165
  If you prefer not to `pip install stata-code` globally, run it ephemerally through [`uv`](https://github.com/astral-sh/uv):
162
166
 
163
167
  ```bash
164
- 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
165
169
  ```
166
170
 
167
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.
168
172
 
169
- #### Manual JSON config (Cursor / Claude Desktop / fallback)
173
+ #### Claude Code via plugin marketplace
174
+
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)
170
185
 
171
- For clients without a `mcp add` CLI, edit the config file directly (`~/.claude/mcp.json`, Cursor settings, Claude Desktop `claude_desktop_config.json`, etc.):
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 |
172
196
 
173
197
  ```json
174
198
  {
@@ -180,12 +204,26 @@ For clients without a `mcp add` CLI, edit the config file directly (`~/.claude/m
180
204
  }
181
205
  ```
182
206
 
183
- 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:
184
208
 
185
209
  ```bash
186
210
  python -m stata_code.mcp
187
211
  ```
188
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
+
189
227
  The MCP server registers 15 tools:
190
228
 
191
229
  | Tool | Purpose |
@@ -196,7 +234,7 @@ The MCP server registers 15 tools:
196
234
  | `get_graph` | Fetch graph bytes behind a `graph://` ref (`ImageContent`) |
197
235
  | `get_matrix` | Fetch matrix payloads behind a `matrix://` ref |
198
236
  | `list_sessions` | Enumerate live sessions |
199
- | `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 |
200
238
  | `reset_session` | Drop a session's data |
201
239
  | `notebook_outline` | Compact per-cell index of a `.ipynb` (cell_id, type, preview) |
202
240
  | `notebook_get_cell` | One cell's full source plus a token-economic outputs summary |
@@ -261,7 +299,20 @@ The companion extension is on the Marketplace as [`brycewang-stanford.stata-code
261
299
  code --install-extension brycewang-stanford.stata-code-vscode
262
300
  ```
263
301
 
264
- 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.
265
316
 
266
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.
267
318
 
@@ -310,14 +361,15 @@ stata_code/
310
361
  │ ├── _refs.py # LRU ref store for log/graph/matrix payloads
311
362
  │ ├── schema.py # Pydantic v2 models for the v1.0 result schema
312
363
  │ ├── errors.py # rc → ErrorKind mapping + suggestion seeds
313
- └── runner.py # the one execute(); collects everything via sfi
364
+ ├── runner.py # in-process execute(); collects everything via sfi
365
+ │ └── _pool.py # subprocess workers for public API / MCP hard timeouts
314
366
  ├── mcp/
315
367
  │ └── server.py # MCP server (15 tools)
316
368
  └── kernel/
317
369
  └── kernel.py # Jupyter kernel
318
370
  ```
319
371
 
320
- `runner.py` is the only place that touches Stata. The Jupyter kernel and MCP server both import from it and only translate results into their own transports.
372
+ `runner.py` is the only place that directly talks to `pystata`. The public Python API and MCP server route calls through `_pool.py`, whose workers call `runner.execute()` in an isolated subprocess; the Jupyter kernel uses the in-process runner for notebook interactivity.
321
373
 
322
374
  ---
323
375
 
@@ -353,7 +405,7 @@ stata_code/
353
405
  - MCP server: 15 tools, including notebook navigation / search / atomic edits and the run-bundle index (`list_runs`)
354
406
  - Jupyter kernel: rewired to the v1.0 pipeline, kernel logos bundled
355
407
  - Matrix size cap + `get_matrix(ref)` for large matrices (>10k cells)
356
- - 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`
357
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`
358
410
  - Persistent run bundles + `list_runs` query over `manifest.json` files (filter by cell / origin / session / since / ok)
359
411
  - JSON Schema artifact auto-generated from `schema.py`: [`schema/run_result.schema.json`](schema/run_result.schema.json)
@@ -363,8 +415,8 @@ stata_code/
363
415
  ### Next Up
364
416
 
365
417
  - Console fallback for Stata 11–16, re-implemented against the v1.0 schema
366
- - Hard timeout / mid-Stata interrupt; design and tradeoffs in [`docs/design/hard_timeout.md`](docs/design/hard_timeout.md)
367
- - 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
368
420
  - **v1.0** — Stable schema, broader Stata edition coverage
369
421
 
370
422
  See [SCHEMA.md §7](SCHEMA.md) for explicitly out-of-scope items.
@@ -375,9 +427,9 @@ See [SCHEMA.md §7](SCHEMA.md) for explicitly out-of-scope items.
375
427
 
376
428
  ```bash
377
429
  pip install -e ".[dev,mcp,kernel]"
378
- pytest # full suite (310 tests)
430
+ pytest # full suite, including Stata tests when Stata is available
379
431
  pytest -m "not stata_required" # CI subset; no Stata needed
380
- pytest -m "stata_required" -v # Stata-only integration tests
432
+ pytest -m "stata_required" -v # real-Stata integration tests only
381
433
  ```
382
434
 
383
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.