stata-code 0.6.1__tar.gz → 0.6.3__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.1 → stata_code-0.6.3}/CHANGELOG.md +141 -0
  2. {stata_code-0.6.1 → stata_code-0.6.3}/PKG-INFO +12 -6
  3. {stata_code-0.6.1 → stata_code-0.6.3}/README.md +10 -5
  4. {stata_code-0.6.1 → stata_code-0.6.3}/SCHEMA.md +16 -19
  5. {stata_code-0.6.1 → stata_code-0.6.3}/pyproject.toml +2 -1
  6. {stata_code-0.6.1 → stata_code-0.6.3}/schema/run_result.schema.json +1 -1
  7. stata_code-0.6.3/scripts/check_versions.py +104 -0
  8. {stata_code-0.6.1 → stata_code-0.6.3}/scripts/export_schema.py +4 -1
  9. stata_code-0.6.3/stata_code/__init__.py +221 -0
  10. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/core/_pool.py +197 -36
  11. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/core/_runtime.py +13 -4
  12. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/core/errors.py +17 -8
  13. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/core/log_artifacts.py +20 -2
  14. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/core/notebook.py +30 -11
  15. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/core/run_index.py +27 -3
  16. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/core/runner.py +63 -18
  17. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/kernel/kernel.py +36 -11
  18. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/mcp/server.py +194 -76
  19. stata_code-0.6.3/tests/conftest.py +51 -0
  20. {stata_code-0.6.1 → stata_code-0.6.3}/tests/test_cancel.py +3 -0
  21. {stata_code-0.6.1 → stata_code-0.6.3}/tests/test_mcp.py +208 -22
  22. stata_code-0.6.3/tests/test_mcp_stdio.py +163 -0
  23. {stata_code-0.6.1 → stata_code-0.6.3}/tests/test_pool.py +47 -0
  24. stata_code-0.6.3/tests/test_public_api.py +71 -0
  25. stata_code-0.6.1/stata_code/__init__.py +0 -102
  26. {stata_code-0.6.1 → stata_code-0.6.3}/.gitignore +0 -0
  27. {stata_code-0.6.1 → stata_code-0.6.3}/LICENSE +0 -0
  28. {stata_code-0.6.1 → stata_code-0.6.3}/LICENSE-POLICY.md +0 -0
  29. {stata_code-0.6.1 → stata_code-0.6.3}/PUBLISHING.md +0 -0
  30. {stata_code-0.6.1 → stata_code-0.6.3}/docs/design/hard_timeout.md +0 -0
  31. {stata_code-0.6.1 → stata_code-0.6.3}/examples/01-basic-regression.md +0 -0
  32. {stata_code-0.6.1 → stata_code-0.6.3}/examples/02-did-card-krueger.md +0 -0
  33. {stata_code-0.6.1 → stata_code-0.6.3}/examples/03-graphs.md +0 -0
  34. {stata_code-0.6.1 → stata_code-0.6.3}/examples/04-multi-session.md +0 -0
  35. {stata_code-0.6.1 → stata_code-0.6.3}/examples/05-large-matrix.md +0 -0
  36. {stata_code-0.6.1 → stata_code-0.6.3}/examples/README.md +0 -0
  37. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/core/__init__.py +0 -0
  38. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/core/_refs.py +0 -0
  39. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/core/schema.py +0 -0
  40. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/kernel/__init__.py +0 -0
  41. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/kernel/__main__.py +0 -0
  42. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/kernel/assets/logo-32x32.png +0 -0
  43. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/kernel/assets/logo-64x64.png +0 -0
  44. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/kernel/assets/logo-svg.svg +0 -0
  45. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/mcp/__init__.py +0 -0
  46. {stata_code-0.6.1 → stata_code-0.6.3}/stata_code/mcp/__main__.py +0 -0
  47. {stata_code-0.6.1 → stata_code-0.6.3}/tests/__init__.py +0 -0
  48. {stata_code-0.6.1 → stata_code-0.6.3}/tests/fixtures/.gitkeep +0 -0
  49. {stata_code-0.6.1 → stata_code-0.6.3}/tests/test_errors.py +0 -0
  50. {stata_code-0.6.1 → stata_code-0.6.3}/tests/test_kernel.py +0 -0
  51. {stata_code-0.6.1 → stata_code-0.6.3}/tests/test_log_artifacts.py +0 -0
  52. {stata_code-0.6.1 → stata_code-0.6.3}/tests/test_notebook.py +0 -0
  53. {stata_code-0.6.1 → stata_code-0.6.3}/tests/test_notebook_phase2.py +0 -0
  54. {stata_code-0.6.1 → stata_code-0.6.3}/tests/test_run_index.py +0 -0
  55. {stata_code-0.6.1 → stata_code-0.6.3}/tests/test_runner.py +0 -0
  56. {stata_code-0.6.1 → stata_code-0.6.3}/tests/test_runtime_discovery.py +0 -0
  57. {stata_code-0.6.1 → stata_code-0.6.3}/tests/test_schema.py +0 -0
  58. {stata_code-0.6.1 → stata_code-0.6.3}/tests/test_schema_artifact.py +0 -0
@@ -6,6 +6,147 @@ to semver-major.minor for the result schema (see `SCHEMA.md` §6).
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## 0.6.3 — 2026-05-10
10
+
11
+ ### Added
12
+
13
+ - **`RefNotFound` exception** — `get_log` / `get_graph` / `get_matrix` now
14
+ raise a typed `RefNotFound` (subclass of `KeyError`) carrying the bad
15
+ ref and a stable `kind` token (`unknown_log_ref` / `unknown_graph_ref` /
16
+ `unknown_matrix_ref`). MCP dispatch maps it to a typed error response
17
+ without string-parsing.
18
+ - **`NotebookError.kind` / `RunIndexError.kind`** — both classes now
19
+ expose a `kind` property so callers don't have to slice the message
20
+ prefix manually.
21
+ - **`SessionPool.list_session_info_detailed`** — partial-failure-aware
22
+ variant of `list_session_info()` that surfaces per-worker `warnings`
23
+ alongside the aggregated `sessions` list. The MCP `list_sessions`
24
+ tool's output schema now includes the optional `warnings` field.
25
+ - **`list_runs.requested_limit`** — echoes the original requested limit
26
+ when the server clamps it to `_LIMIT_MAX`, so a caller asking for
27
+ `limit=1000` can distinguish "scan was capped at 500 rows" from "the
28
+ manifest dir really has more than 500 rows".
29
+ - **Server capabilities resource now includes prompts.** Reading
30
+ `stata://server/capabilities` returns tools, resource templates, AND
31
+ prompts in a single document — clients no longer need a follow-up
32
+ `list_prompts` round-trip just to enumerate the full surface.
33
+
34
+ ### Changed
35
+
36
+ - **Test hygiene** — a `tests/conftest.py` snapshot/restores `_refs._store`
37
+ per test and shuts the default subprocess pool down at session end.
38
+ Closes a class of intermittent cross-test failures caused by ref-store
39
+ pollution from earlier MCP / pool tests.
40
+ - **Subprocess cancellation race** — `SessionPool.execute()` now
41
+ re-checks `_cancel_pending` after `_get_or_spawn` so a `request_cancel`
42
+ that lands during worker spawn fires on the in-flight call instead of
43
+ the next one.
44
+ - **`stata_info` edition casing is now consistent** — the top-level
45
+ `edition` field mirrors `stata.edition` (the enum value, e.g. `MP`)
46
+ verbatim. Previously the pool path lowercased it to `mp` while the
47
+ in-process path emitted `MP`, so the same payload could disagree with
48
+ itself.
49
+ - **All MCP tool input schemas now declare `additionalProperties: false`.**
50
+ Typos like `originPath` (camelCase) or misspelled `log_lin_head` are
51
+ now rejected up-front with a typed validation error instead of silently
52
+ producing a wrong run.
53
+ - **`cancel_session` and `reset_session` output schemas split.** Each
54
+ tool now advertises only the fields it actually returns; the previous
55
+ shared schema overpromised on one side and underpromised on the other.
56
+ - **`list_resources` caps ref-backed entries at 256.** Long-lived
57
+ servers no longer return thousands of stale ref resources from
58
+ forgotten sessions — the most recently used 256 win.
59
+ - **Pystata edition init now preserves the full error trail.** When all
60
+ three editions (MP / SE / BE) fail, the `PystataNotAvailable` message
61
+ lists each attempt's error rather than collapsing to the last one.
62
+
63
+ ### Fixed
64
+
65
+ - **Jupyter kernel `implementation_version`** is now derived from
66
+ `stata_code.__version__` rather than a separate `0.2.0` literal.
67
+ - **Jupyter kernel mypy `Invalid base class`** error — the dynamic
68
+ `Kernel if _HAS_IPYKERNEL else object` base class confused mypy.
69
+ Hidden behind a `TYPE_CHECKING` gate now; runtime behavior unchanged.
70
+ - **`do_execute` / `do_inspect` signatures** are now LSP-compatible
71
+ with the latest `ipykernel.kernelbase.Kernel` (accepts `cell_meta`,
72
+ `cell_id`, `omit_sections`).
73
+ - **Schema `$id` URL** corrected from `stata_code` to `stata-code`
74
+ (matches the actual GitHub repository name).
75
+ - **VS Code MCP handshake** version is now read from `package.json`
76
+ via `resolveJsonModule`, removing the former fifth sync site.
77
+ `check_versions.py` still validates the literal form
78
+ when present, so reverting to a hardcoded string fails CI.
79
+ - **Release tag glob tightened** — `v[0-9]*.[0-9]*.[0-9]*` instead of
80
+ `v[0-9]*` so stray tags without semver-style dot separators no longer
81
+ trigger the release workflow.
82
+ - **Release pipeline now gates on ruff** so a direct push to `main`
83
+ cannot ship un-linted code.
84
+ - **Mypy CI now covers `stata_code/mcp` and `stata_code/kernel`** in
85
+ addition to `core`, with the optional `mcp` / `kernel` extras
86
+ installed so `Server`, `Tool`, and `Kernel` symbols resolve.
87
+ - **`COMMON_STATA_COMMANDS` deduplicated** — dropped the short forms
88
+ (`cap` / `qui` / `noi` / `mat` / `di`) that were producing
89
+ near-duplicate "did you mean" hits from `difflib`. The long forms
90
+ cover the same fuzzy-match neighbourhood.
91
+ - **`_unique_dir` / `_unique_file`** fall back to a UUID suffix after
92
+ 998 collisions instead of raising `FileExistsError`. The original
93
+ behaviour blocked the run on conditions that almost always indicate a
94
+ filesystem issue rather than a name clash.
95
+ - **Dead code removed** — `_info_payload` (orphaned helper) in the MCP
96
+ server, `_truncate` in `notebook.py`, and the unused `index` / `source`
97
+ parameters on `_ensure_native_id`.
98
+
99
+ ## 0.6.2 — 2026-05-08
100
+
101
+ Aggregated from the prior `Unreleased` section; covers 0.6.1 and 0.6.2.
102
+
103
+ ### Added
104
+
105
+ - **Release version guard.** `scripts/check_versions.py` and the CI /
106
+ release workflows now verify that the Python package, MCP server, VS Code
107
+ package, VS Code MCP handshake, and release tag all use the same version.
108
+ - **VS Code MCP launch tests.** The extension's server-launch candidate
109
+ builder is now a pure tested module covering local `.venv` / `venv`
110
+ discovery, configured Python interpreters, inline command parsing, and
111
+ environment construction.
112
+ - **VS Code packaging smoke test.** The default CI now builds a VSIX artifact
113
+ with the same local `vsce` package command used by release packaging, so
114
+ package-manifest and `.vscodeignore` mistakes fail before release day.
115
+ - **VS Code extension bundling.** The extension now bundles its TypeScript
116
+ entrypoint with `esbuild`, excluding `node_modules` and test/build support
117
+ files from the shipped VSIX.
118
+ - **VS Code bundled dependency notices.** `THIRD_PARTY_NOTICES.md` now lists
119
+ the npm packages included in the bundled extension output and their license
120
+ terms.
121
+ - **PyPI publish verification.** The release workflow now polls PyPI's
122
+ per-version JSON endpoint after the official publish job, so trusted
123
+ publisher failures surface as a failed release run instead of being hidden
124
+ by `continue-on-error`.
125
+
126
+ ### Changed
127
+
128
+ - **Package-level Python API is subprocess-backed.** `stata_code.run()`,
129
+ `execute()`, and `is_available()` now go through the same worker pool as
130
+ the MCP server, preserving hard timeout behavior and avoiding caller-process
131
+ stdout redirection by `pystata`.
132
+ - **CI treats core type errors as blocking.** `mypy stata_code/core` is now a
133
+ hard failure, and the default test workflow also compiles and tests the VS
134
+ Code extension.
135
+ - **VS Code npm installs are locked.** The extension now ships
136
+ `package-lock.json`, and CI / release workflows use `npm ci` for reproducible
137
+ dependency installs.
138
+ - **VS Code bundle target matches the extension host floor.** The esbuild
139
+ target is `node18`, keeping the published bundle aligned with the current
140
+ `engines.vscode` lower bound.
141
+ - **Python 3.13 is in the support matrix.** CI now runs the no-Stata test
142
+ suite on Python 3.13, and package metadata advertises the 3.13 classifier.
143
+
144
+ ### Fixed
145
+
146
+ - **Cancelled in-flight runs report incomplete logs.** If a live worker is
147
+ killed by `cancel_session`, the synthetic cancelled result now marks
148
+ `log.complete=false`, matching timeout and adapter-crash behavior.
149
+
9
150
  ## [0.6.0] — 2026-05-08
10
151
 
11
152
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stata-code
3
- Version: 0.6.1
3
+ Version: 0.6.3
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
@@ -37,7 +38,7 @@ Requires-Dist: mcp>=1.27; extra == 'mcp'
37
38
  Description-Content-Type: text/markdown
38
39
 
39
40
  <p align="center">
40
- <img src="branding/logo/horizontal@1024.png" alt="stata-code logo" width="520" />
41
+ <img src="https://raw.githubusercontent.com/brycewang-stanford/stata-code/main/branding/logo/horizontal@1024.png" alt="stata-code logo" width="520" />
41
42
  </p>
42
43
 
43
44
  <p align="center">
@@ -59,7 +60,7 @@ Description-Content-Type: text/markdown
59
60
  [![GitHub stars](https://img.shields.io/github/stars/brycewang-stanford/stata-code?style=social)](https://github.com/brycewang-stanford/stata-code)
60
61
 
61
62
  <p align="center">
62
- <img src="branding/github-instructions.png" alt="stata-code: agent-native Stata bridge — one Python core, multiple frontends (Jupyter kernel, MCP server, VS Code extension)" width="720" />
63
+ <img src="https://raw.githubusercontent.com/brycewang-stanford/stata-code/main/branding/github-instructions.png" alt="stata-code: agent-native Stata bridge — one Python core, multiple frontends (Jupyter kernel, MCP server, VS Code extension)" width="720" />
63
64
  </p>
64
65
 
65
66
  > Agent-native Stata bridge — **one Python core, multiple frontends**.
@@ -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. Current test suite: 329 passing tests across schema, runner, MCP, kernel, notebook, run-index, subprocess-pool, and VS Code modules. 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
 
@@ -348,14 +353,15 @@ stata_code/
348
353
  │ ├── _refs.py # LRU ref store for log/graph/matrix payloads
349
354
  │ ├── schema.py # Pydantic v2 models for the v1.0 result schema
350
355
  │ ├── errors.py # rc → ErrorKind mapping + suggestion seeds
351
- └── runner.py # the one execute(); collects everything via sfi
356
+ ├── runner.py # in-process execute(); collects everything via sfi
357
+ │ └── _pool.py # subprocess workers for public API / MCP hard timeouts
352
358
  ├── mcp/
353
359
  │ └── server.py # MCP server (15 tools)
354
360
  └── kernel/
355
361
  └── kernel.py # Jupyter kernel
356
362
  ```
357
363
 
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.
364
+ `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
365
 
360
366
  ---
361
367
 
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="branding/logo/horizontal@1024.png" alt="stata-code logo" width="520" />
2
+ <img src="https://raw.githubusercontent.com/brycewang-stanford/stata-code/main/branding/logo/horizontal@1024.png" alt="stata-code logo" width="520" />
3
3
  </p>
4
4
 
5
5
  <p align="center">
@@ -21,7 +21,7 @@
21
21
  [![GitHub stars](https://img.shields.io/github/stars/brycewang-stanford/stata-code?style=social)](https://github.com/brycewang-stanford/stata-code)
22
22
 
23
23
  <p align="center">
24
- <img src="branding/github-instructions.png" alt="stata-code: agent-native Stata bridge — one Python core, multiple frontends (Jupyter kernel, MCP server, VS Code extension)" width="720" />
24
+ <img src="https://raw.githubusercontent.com/brycewang-stanford/stata-code/main/branding/github-instructions.png" alt="stata-code: agent-native Stata bridge — one Python core, multiple frontends (Jupyter kernel, MCP server, VS Code extension)" width="720" />
25
25
  </p>
26
26
 
27
27
  > Agent-native Stata bridge — **one Python core, multiple frontends**.
@@ -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. Current test suite: 329 passing tests across schema, runner, MCP, kernel, notebook, run-index, subprocess-pool, and VS Code modules. 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
 
@@ -310,14 +314,15 @@ stata_code/
310
314
  │ ├── _refs.py # LRU ref store for log/graph/matrix payloads
311
315
  │ ├── schema.py # Pydantic v2 models for the v1.0 result schema
312
316
  │ ├── errors.py # rc → ErrorKind mapping + suggestion seeds
313
- └── runner.py # the one execute(); collects everything via sfi
317
+ ├── runner.py # in-process execute(); collects everything via sfi
318
+ │ └── _pool.py # subprocess workers for public API / MCP hard timeouts
314
319
  ├── mcp/
315
320
  │ └── server.py # MCP server (15 tools)
316
321
  └── kernel/
317
322
  └── kernel.py # Jupyter kernel
318
323
  ```
319
324
 
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.
325
+ `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
326
 
322
327
  ---
323
328
 
@@ -580,7 +580,6 @@ When v2 ships, v1 is supported by frontends for at least 6 months. Servers MAY e
580
580
  Explicitly *not* in this version, to keep the surface small:
581
581
 
582
582
  - **Streaming logs.** All output is batched at end-of-call. `log.complete: false` is reserved for this in v2. Streams (Stata vs Python vs Mata) may be separated under a future `log.streams` field.
583
- - **Hard timeout enforcement / mid-Stata interrupt.** `cancel(session_id)` is implemented as a *cooperative* signal that short-circuits the next `execute()` call before pystata is invoked; it does not interrupt code that is already mid-`stata.run()`. Hard interruption requires a subprocess-based runtime (post-v0.2).
584
583
  - **Distributed / remote sessions.** Sessions are per-process. `session_id` reserves `:` for future host-prefixing.
585
584
  - **Authentication / authorization.** Local trusted environment is assumed.
586
585
  - **Mata internals.** Mata code runs (`stata.run("mata: ...")`) but Mata-specific return values aren't surfaced beyond what `r()` carries.
@@ -596,7 +595,7 @@ Explicitly *not* in this version, to keep the surface small:
596
595
  This section tracks how much of the schema is wired up in code. Not normative
597
596
  — the contract above is the contract — but useful as a release checklist.
598
597
 
599
- ### Implemented in v0.2 (2026-05)
598
+ ### Implemented through v0.6 (2026-05)
600
599
 
601
600
  - Log `head` / `tail` / `truncated` / `complete` / `error_window` / `ref`
602
601
  with an in-memory ref store backing `get_log`.
@@ -634,23 +633,21 @@ This section tracks how much of the schema is wired up in code. Not normative
634
633
  - LRU eviction on the ref store (default cap 256) keeps long-running
635
634
  producers from growing unboundedly.
636
635
 
637
- - **Cooperative cancellation** via `cancel(session_id)` /
638
- `clear_cancel(session_id)` / `is_cancel_pending(session_id)`,
639
- exposed as a Python API and as the MCP `cancel_session` tool.
640
- Short-circuits the next `execute()` call for the named session and
641
- returns a `RunResult` with `ok=false`, `rc=-3`,
642
- `error.kind="cancelled"`. Cooperative semantics — does not
643
- interrupt code that is already mid-`stata.run()`.
644
-
645
- ### Still deferred (post-v0.2)
646
-
647
- - **Hard timeout / mid-Stata interrupt.** `timeout_ms` is accepted by
648
- `execute()` but not yet enforced; cancellation is cooperative-only
649
- (does not interrupt code already in-flight). pystata's in-process
650
- model has no clean cancel primitive — v0.3 will move long calls
651
- into a subprocess pool with signal-based cancellation. Design
652
- constraints, options considered, and an effort estimate are
653
- written up in [`docs/design/hard_timeout.md`](docs/design/hard_timeout.md).
636
+ - **Subprocess-backed hard timeout and cancellation** via the public
637
+ package API, MCP `stata_run`, and the subprocess session pool.
638
+ `timeout_ms` returns `ok=false`, `rc=-2`, `error.kind="timeout"`
639
+ after terminating the worker. `cancel(session_id)` /
640
+ `clear_cancel(session_id)` / `is_cancel_pending(session_id)` and
641
+ MCP `cancel_session` return `ok=false`, `rc=-3`,
642
+ `error.kind="cancelled"`; an in-flight pool worker is terminated and a
643
+ not-yet-started run is short-circuited before Stata receives code.
644
+
645
+ ### Still deferred
646
+
647
+ - **In-process hard timeout.** `stata_code.core.runner.execute()` still
648
+ runs inside the caller process and cannot preempt code already inside
649
+ `pystata`. Use the package-level `stata_code.run()` / `execute()` or
650
+ MCP server for subprocess-backed timeouts and cancellation.
654
651
  - **Console fallback for Stata 11–16.** Earlier scaffold's
655
652
  `ConsoleFallback` was deleted in v0.2 (it produced legacy
656
653
  `StataResult` and didn't fit the new pipeline). v0.3 will reintroduce
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "stata-code"
7
- version = "0.6.1"
7
+ version = "0.6.3"
8
8
  description = "Agent-native Stata bridge — one core, multiple frontends (MCP, Jupyter, VSCode)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -21,6 +21,7 @@ classifiers = [
21
21
  "Programming Language :: Python :: 3.10",
22
22
  "Programming Language :: Python :: 3.11",
23
23
  "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
24
25
  "Topic :: Scientific/Engineering :: Mathematics",
25
26
  "Topic :: Software Development :: Libraries :: Python Modules",
26
27
  ]
@@ -802,7 +802,7 @@
802
802
  "type": "object"
803
803
  }
804
804
  },
805
- "$id": "https://github.com/brycewang-stanford/stata_code/schema/run_result.schema.json",
805
+ "$id": "https://raw.githubusercontent.com/brycewang-stanford/stata-code/main/schema/run_result.schema.json",
806
806
  "$schema": "https://json-schema.org/draft/2020-12/schema",
807
807
  "additionalProperties": true,
808
808
  "description": "Machine-readable JSON Schema for the v1.0 result envelope returned by stata_code.run(). The normative contract is SCHEMA.md; this file is auto-generated from stata_code.core.schema.RunResult and kept in sync by tests/test_schema_artifact.py.",
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env python3
2
+ """Verify that every release version literal is aligned."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import re
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ ROOT = Path(__file__).resolve().parents[1]
13
+
14
+
15
+ def _read(rel: str) -> str:
16
+ return ROOT.joinpath(rel).read_text(encoding="utf-8")
17
+
18
+
19
+ def _extract(rel: str, pattern: str) -> str:
20
+ match = re.search(pattern, _read(rel), re.MULTILINE)
21
+ if match is None:
22
+ raise RuntimeError(f"could not find version in {rel}")
23
+ return match.group(1)
24
+
25
+
26
+ def _tag_version(tag: str) -> str:
27
+ for prefix in ("vscode-v", "v"):
28
+ if tag.startswith(prefix):
29
+ return tag[len(prefix):]
30
+ raise RuntimeError(f"tag must start with v or vscode-v, got {tag!r}")
31
+
32
+
33
+ def main() -> int:
34
+ parser = argparse.ArgumentParser(
35
+ description="Check pyproject/Python/MCP/VS Code version literals."
36
+ )
37
+ parser.add_argument(
38
+ "--tag",
39
+ help="Optional Git tag to compare against, e.g. vX.Y.Z or vscode-vX.Y.Z.",
40
+ )
41
+ args = parser.parse_args()
42
+
43
+ versions = {
44
+ "pyproject.toml": _extract(
45
+ "pyproject.toml", r'(?m)^\s*version\s*=\s*"([^"]+)"'
46
+ ),
47
+ "stata_code/__init__.py": _extract(
48
+ "stata_code/__init__.py", r'(?m)^__version__\s*=\s*"([^"]+)"'
49
+ ),
50
+ "stata_code/mcp/server.py": _extract(
51
+ "stata_code/mcp/server.py", r'(?m)^__version__\s*=\s*"([^"]+)"'
52
+ ),
53
+ "vscode/package.json": json.loads(_read("vscode/package.json"))["version"],
54
+ }
55
+
56
+ # mcpClient.ts used to carry a literal version; we now resolve it at
57
+ # build time via ``import { version } from "../package.json"``. If the
58
+ # import is present, there is no separate sync site. If someone later
59
+ # reverts to a literal, parse it back so the alignment check still
60
+ # catches drift.
61
+ #
62
+ # KNOWN LIMITATION: when the import is present this script does NOT
63
+ # also scan the file for a stray hardcoded literal alongside the
64
+ # import (e.g. a merge-conflict artifact). The check is import-OR-
65
+ # literal, not import-AND-no-stray-literal. The Python sync sites
66
+ # don't have that escape hatch — they're always literal-checked.
67
+ # If this asymmetry ever causes a bad release, tighten by failing
68
+ # whenever BOTH the import and a literal handshake string co-exist.
69
+ mcp_client_src = _read("vscode/src/mcpClient.ts")
70
+ if 'from "../package.json"' not in mcp_client_src:
71
+ match = re.search(
72
+ r'\{\s*name:\s*"stata-code-vscode",\s*version:\s*"([^"]+)"\s*\}',
73
+ mcp_client_src,
74
+ )
75
+ if match is None:
76
+ raise RuntimeError(
77
+ "vscode/src/mcpClient.ts no longer imports the version from "
78
+ "../package.json AND does not declare a literal — restore "
79
+ "either form."
80
+ )
81
+ versions["vscode/src/mcpClient.ts"] = match.group(1)
82
+
83
+ unique = sorted(set(versions.values()))
84
+ ok = len(unique) == 1
85
+ expected = unique[0] if ok else None
86
+
87
+ if args.tag:
88
+ tag_version = _tag_version(args.tag)
89
+ if expected != tag_version:
90
+ ok = False
91
+ versions[f"tag:{args.tag}"] = tag_version
92
+
93
+ if ok:
94
+ print(f"ok: all release versions are {expected}")
95
+ return 0
96
+
97
+ print("version mismatch:", file=sys.stderr)
98
+ for source, version in versions.items():
99
+ print(f" {source}: {version}", file=sys.stderr)
100
+ return 1
101
+
102
+
103
+ if __name__ == "__main__":
104
+ raise SystemExit(main())
@@ -31,7 +31,10 @@ def build_schema() -> dict:
31
31
  schema = RunResult.model_json_schema()
32
32
  # Stable, human-friendly framing for downstream consumers.
33
33
  schema["$schema"] = "https://json-schema.org/draft/2020-12/schema"
34
- schema["$id"] = "https://github.com/brycewang-stanford/stata_code/schema/run_result.schema.json"
34
+ schema["$id"] = (
35
+ "https://raw.githubusercontent.com/brycewang-stanford/stata-code/"
36
+ "main/schema/run_result.schema.json"
37
+ )
35
38
  schema["title"] = "stata_code RunResult (v1.0)"
36
39
  schema["description"] = (
37
40
  "Machine-readable JSON Schema for the v1.0 result envelope returned by "