stata-code 0.9.0__tar.gz → 0.11.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stata_code-0.9.0 → stata_code-0.11.0}/.gitignore +2 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/CHANGELOG.md +337 -7
- {stata_code-0.9.0 → stata_code-0.11.0}/PKG-INFO +133 -28
- {stata_code-0.9.0 → stata_code-0.11.0}/PUBLISHING.md +16 -7
- stata_code-0.9.0/README.md → stata_code-0.11.0/README.en.md +132 -27
- stata_code-0.11.0/README.md +567 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/SCHEMA.md +119 -32
- {stata_code-0.9.0 → stata_code-0.11.0}/docs/competitive-landscape.md +60 -2
- {stata_code-0.9.0 → stata_code-0.11.0}/docs/industry-leader-roadmap.md +1 -1
- stata_code-0.11.0/docs/quickstart.zh.md +102 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/pyproject.toml +5 -2
- {stata_code-0.9.0 → stata_code-0.11.0}/schema/run_result.schema.json +88 -0
- stata_code-0.11.0/scripts/build_standalone.py +81 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/__init__.py +77 -1
- stata_code-0.11.0/stata_code/cli.py +538 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/_pool.py +432 -93
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/_runtime.py +13 -2
- stata_code-0.11.0/stata_code/core/console.py +818 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/errors.py +82 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/estimation.py +132 -36
- stata_code-0.11.0/stata_code/core/jobs.py +200 -0
- stata_code-0.11.0/stata_code/core/lint.py +246 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/log_artifacts.py +62 -2
- stata_code-0.11.0/stata_code/core/policy.py +375 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/runner.py +695 -99
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/schema.py +61 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/doctor.py +22 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/kernel/kernel.py +12 -3
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/mcp/server.py +472 -17
- stata_code-0.11.0/stata_code/mcp_setup.py +174 -0
- stata_code-0.11.0/tests/test_agent_ergonomics.py +867 -0
- stata_code-0.11.0/tests/test_benchmark.py +59 -0
- stata_code-0.11.0/tests/test_bugfix_regressions.py +556 -0
- stata_code-0.11.0/tests/test_cli.py +213 -0
- stata_code-0.11.0/tests/test_console.py +304 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_errors.py +5 -1
- stata_code-0.11.0/tests/test_lint.py +89 -0
- stata_code-0.11.0/tests/test_lint_mcp.py +67 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_mcp.py +82 -134
- stata_code-0.11.0/tests/test_mcp_kernel_extra.py +1087 -0
- stata_code-0.11.0/tests/test_mcp_transport.py +51 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_new_tools.py +8 -6
- stata_code-0.11.0/tests/test_policy.py +156 -0
- stata_code-0.11.0/tests/test_pool_refs_extra.py +993 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_runner.py +141 -5
- stata_code-0.11.0/tests/test_runner_helpers.py +1213 -0
- stata_code-0.9.0/stata_code/cli.py +0 -85
- stata_code-0.9.0/tests/test_mcp_stdio.py +0 -163
- {stata_code-0.9.0 → stata_code-0.11.0}/LICENSE +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/LICENSE-POLICY.md +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/docs/design/hard_timeout.md +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/examples/01-basic-regression.md +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/examples/02-did-card-krueger.md +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/examples/03-graphs.md +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/examples/04-multi-session.md +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/examples/05-large-matrix.md +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/examples/06-cross-stack-parity-audit.md +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/examples/07-data-mcp-handoff.md +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/examples/README.md +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/scripts/build_skill_zip.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/scripts/check_github_actions.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/scripts/check_versions.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/scripts/export_schema.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/__main__.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/__init__.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/_refs.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/handoff.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/notebook.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/provenance.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/run_index.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/kernel/__init__.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/kernel/__main__.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/kernel/assets/logo-32x32.png +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/kernel/assets/logo-64x64.png +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/kernel/assets/logo-svg.svg +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/mcp/__init__.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/mcp/__main__.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/__init__.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/conftest.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/fixtures/.gitkeep +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_cancel.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_doctor.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_estimation.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_github_actions.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_handoff.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_kernel.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_log_artifacts.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_method_prompts.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_notebook.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_notebook_phase2.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_pool.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_provenance.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_public_api.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_real_stata.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_release_versions.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_run_index.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_runtime_discovery.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_schema.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_schema_artifact.py +0 -0
- {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_skill_package.py +0 -0
|
@@ -4,6 +4,338 @@ All notable changes to `stata-code` are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the project adheres
|
|
5
5
|
to semver-major.minor for the result schema (see `SCHEMA.md` §6).
|
|
6
6
|
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## 0.11.0 — 2026-07-28
|
|
10
|
+
|
|
11
|
+
An agent-ergonomics release, driven by a report from an agent that used the MCP
|
|
12
|
+
server to write a full empirical paper. Each item below is a cost that agent
|
|
13
|
+
actually paid.
|
|
14
|
+
|
|
15
|
+
**Minor, not patch:** `include_results` defaults to `"scalars"`, so `r()` / `e()`
|
|
16
|
+
matrices now arrive as `matrix://` stubs instead of inline values. Pass
|
|
17
|
+
`include_results="full"` for the previous shape. `results.estimation` is
|
|
18
|
+
unaffected — and is now *more* accurate, since two silent numeric bugs in it are
|
|
19
|
+
fixed. The Jupyter kernel and the `stata-code run` CLI keep `"full"`.
|
|
20
|
+
|
|
21
|
+
### Agent-ergonomics pass
|
|
22
|
+
|
|
23
|
+
#### Result payloads are now bounded (and correct)
|
|
24
|
+
|
|
25
|
+
- **`include_results`** (`"none" | "scalars" | "full"`, default **`"scalars"`**).
|
|
26
|
+
A single estimation used to encode the same numbers four times over — `e(b)`,
|
|
27
|
+
`e(V)`'s row/column labels, `e(beta)`, and `r(table)` — on top of
|
|
28
|
+
`results.estimation`. Under the new default, scalars and macros stay inline
|
|
29
|
+
and every matrix becomes a `matrix://` stub carrying only its shape
|
|
30
|
+
(`n_rows` / `n_cols`); values and labels remain retrievable with
|
|
31
|
+
`get_matrix(ref)`. A 123-term regression's envelope drops from ~57 KB to
|
|
32
|
+
~28 KB. **This is a behaviour change** — pass `include_results="full"` for
|
|
33
|
+
the previous shape. The Jupyter kernel keeps `"full"`, since a notebook has
|
|
34
|
+
no token budget to defend.
|
|
35
|
+
- **`include_estimation`** (`"none" | "summary" | "full"`, default `"full"`)
|
|
36
|
+
and **`max_coefficients`**. `"summary"` keeps the model-level block and
|
|
37
|
+
diagnostics but drops per-term rows — the right setting for specifications
|
|
38
|
+
dominated by `i.year i.firm` nuisance terms. `estimation.n_coefficients`
|
|
39
|
+
always reports the model's true term count and
|
|
40
|
+
`estimation.coefficients_truncated` flags any cut, so trimmed output can
|
|
41
|
+
never be mistaken for a smaller model.
|
|
42
|
+
|
|
43
|
+
#### Fixed: two silent numeric-correctness bugs
|
|
44
|
+
|
|
45
|
+
- **Standard errors were blanked whenever `e(V)` was returned by reference.**
|
|
46
|
+
Any model whose VCV exceeded the inline cell cap (roughly >100 terms) came
|
|
47
|
+
back with `se`, `statistic`, `p_value` and both CI bounds `null` for *every*
|
|
48
|
+
coefficient, while still reporting `source: "e_b_v"` as though inference had
|
|
49
|
+
been computed. The estimation builder now resolves `matrix://` refs, so the
|
|
50
|
+
wire representation no longer degrades the numbers. `e(b)` by reference is
|
|
51
|
+
resolved too, where it previously suppressed the estimation block entirely.
|
|
52
|
+
- **`r()` did not survive to the next call.** The runner's own probes are all
|
|
53
|
+
r-class (`graph dir` before a run, `graph export` after), so the canonical
|
|
54
|
+
two-call pattern — `summarize price`, then `display r(mean)` — read back a
|
|
55
|
+
missing value. All internal housekeeping now runs inside a Stata
|
|
56
|
+
`_return hold` / `_return restore` pair. Pre-existing; the new `log query`
|
|
57
|
+
probe would have widened it to runs with `include_graphs="none"`.
|
|
58
|
+
- **Stata's missing values leaked as `8.988e+307`.** `sfi` returns system
|
|
59
|
+
missing (`.`) and extended missings (`.a`–`.z`) as ordinary doubles at or
|
|
60
|
+
above `2^1023`. These reached the wire unconverted in `r()`/`e()` scalars and
|
|
61
|
+
in every matrix cell — an omitted base level's standard error arrived as a
|
|
62
|
+
number an agent would format into a table. All numerics now normalize to
|
|
63
|
+
`null`, matching what SCHEMA.md already required of scalars.
|
|
64
|
+
|
|
65
|
+
#### Errors inside `do`-files are now located
|
|
66
|
+
|
|
67
|
+
- `error.line` and `error.context` are resolved **inside the invoked script**.
|
|
68
|
+
A failing `do "analysis.do"` previously produced `line: null`, empty
|
|
69
|
+
before/after context, and `message: "end of do-file"` — Stata's epilogue
|
|
70
|
+
rather than the diagnosis. The transcript parser now skips structural
|
|
71
|
+
boilerplate (`end of do-file`, `--Break--`), folds `///` continuation
|
|
72
|
+
fragments back into one logical command, and resolves the failing command
|
|
73
|
+
against the script on disk. New `error.source_file` names the file that
|
|
74
|
+
`error.line` indexes into.
|
|
75
|
+
- **A failed run now produces a log.** pystata raises the whole transcript as
|
|
76
|
+
its exception message and leaves stdout empty, so failures used to come back
|
|
77
|
+
with no `log.head`, no `log://` ref and nothing for `search_log` to search —
|
|
78
|
+
exactly when the transcript matters most.
|
|
79
|
+
|
|
80
|
+
#### A failed run can no longer poison its session
|
|
81
|
+
|
|
82
|
+
- New **`auto_close_logs`** (default `true`) closes log handles that a *failed*
|
|
83
|
+
run opened. A script aborting between `log using` and `log close` previously
|
|
84
|
+
left the handle dangling, and every subsequent run in that session died with
|
|
85
|
+
r(604) — an error about a different run, with no suggestions attached.
|
|
86
|
+
Handles opened by earlier runs are left alone; a `log_closed` warning records
|
|
87
|
+
what was closed.
|
|
88
|
+
- r(604) and r(606) are now the typed `ErrorKind.LOG_STATE` with Stata's
|
|
89
|
+
official labels and rc-specific fixes (`capture log close _all` for 604,
|
|
90
|
+
`capture log close` guarding for 606), and `recovery.retriable = true`.
|
|
91
|
+
|
|
92
|
+
#### Long runs no longer block the caller
|
|
93
|
+
|
|
94
|
+
- **`timeout_ms` is an advertised `stata_run` argument.** It was enforced but
|
|
95
|
+
not exposed, so passing it was an input-validation error.
|
|
96
|
+
- **`run_in_background`** returns a `job_id` immediately; poll with the new
|
|
97
|
+
**`stata_run_status`** tool (`wait_ms` blocks up to 60 s instead of
|
|
98
|
+
busy-polling) and enumerate with **`list_background_runs`**. Intended for
|
|
99
|
+
bootstraps, permutation tests and grouped estimation loops.
|
|
100
|
+
- **Fixed: the worker lock was acquired without a deadline**, so a call queued
|
|
101
|
+
behind a long run blocked indefinitely no matter what `timeout_ms` said.
|
|
102
|
+
`timeout_ms` now budgets the whole call, queueing included, and contention
|
|
103
|
+
returns the new `rc=-5` / `error.kind="session_busy"` — distinct from a
|
|
104
|
+
timeout because nothing was submitted to Stata and the healthy worker is
|
|
105
|
+
*not* killed.
|
|
106
|
+
|
|
107
|
+
#### Inline graphs are visible again
|
|
108
|
+
|
|
109
|
+
- `include_graphs: "inline"` now delivers graph bytes as MCP `ImageContent`
|
|
110
|
+
blocks instead of base64 inside a JSON string. The old form was unreadable to
|
|
111
|
+
a vision-capable client and was pure token cost. `graphs[].inline` is cleared
|
|
112
|
+
from the structured body so the bytes cross the wire once, and
|
|
113
|
+
`inline_delivered` records the outcome. At most 4 images per response (the
|
|
114
|
+
rest stay reachable via `get_graph(ref)`, reported as an
|
|
115
|
+
`inline_graphs_truncated` warning); `pdf` is never sent as an image block.
|
|
116
|
+
|
|
117
|
+
#### Generated files are reported
|
|
118
|
+
|
|
119
|
+
- New top-level **`outputs`** lists files a run created or modified in its
|
|
120
|
+
working directory (`esttab` tables, exported graphs, saved `.dta`), with
|
|
121
|
+
`path` / `bytes` / `created`. Previously this required
|
|
122
|
+
`persist_log_files=true` *and* `origin_path` — a dependency that was not
|
|
123
|
+
obvious from `persist_generated_files` defaulting to `true`, so the field was
|
|
124
|
+
always `null` in practice. Controlled by the new `track_output_files`
|
|
125
|
+
(default `true`); working directories above 5,000 files skip detection and
|
|
126
|
+
say so with an `output_tracking_skipped` warning rather than reporting a
|
|
127
|
+
partial answer.
|
|
128
|
+
|
|
129
|
+
### Added
|
|
130
|
+
|
|
131
|
+
- The VS Code extension now accepts Stata's familiar `Ctrl+D` shortcut
|
|
132
|
+
(`Cmd+D` on macOS) for running the selection or current line, while retaining
|
|
133
|
+
`Cmd/Ctrl+Enter`. The extension README also documents how to customize the
|
|
134
|
+
`stataCode.runSelection` keybinding and resolve conflicts.
|
|
135
|
+
|
|
136
|
+
## 0.10.0 — 2026-07-24
|
|
137
|
+
|
|
138
|
+
Surface-coverage and safety additions so every Stata usage scenario — MCP,
|
|
139
|
+
Jupyter, VS Code, and now a plain Bash/terminal — routes through the same typed
|
|
140
|
+
engine, and so an autonomous agent loop cannot escape to the OS. Also broadens
|
|
141
|
+
execution to **Stata 13+ without pystata** (console backend) and adds a
|
|
142
|
+
zero-Python standalone binary and one-click VS Code onboarding. The default
|
|
143
|
+
README is now Chinese (`README.md`); the English README moves to `README.en.md`.
|
|
144
|
+
|
|
145
|
+
### Added
|
|
146
|
+
|
|
147
|
+
- **Command-safety guard** (`stata_code.core.policy`). OS-escape / file-deletion
|
|
148
|
+
commands (`shell`, `winexec`, `erase`, `rm`, `rmdir`, and the `!` shell escape)
|
|
149
|
+
are screened out of submitted code *before* Stata runs, at both the
|
|
150
|
+
subprocess-pool boundary and the in-process runner. A blocked run returns
|
|
151
|
+
`ok=false`, `rc=-4`, `error.kind="policy_blocked"` (a new 32nd `ErrorKind`)
|
|
152
|
+
without touching Stata. Configurable from the environment so it crosses the
|
|
153
|
+
worker boundary: `STATA_CODE_COMMAND_POLICY` (`enforce` default / `warn` /
|
|
154
|
+
`off`), `STATA_CODE_POLICY_ALLOW`, `STATA_CODE_POLICY_BLOCK`. It is a guard
|
|
155
|
+
rail, not a sandbox.
|
|
156
|
+
- **`stata-code run`** — execute a `.do` file, one or more `-e` snippets, or
|
|
157
|
+
stdin through the subprocess pool and print the `RunResult` (text summary or
|
|
158
|
+
`--json`), with `--session`, `--timeout-ms`, `--full-log`, and `--graphs DIR`.
|
|
159
|
+
Exit code is `0` on success, `1` on a Stata/adapter error. This is the
|
|
160
|
+
Bash / plain-terminal surface: any agent that can shell out gets the full
|
|
161
|
+
structured error loop without MCP.
|
|
162
|
+
- **`stata-code setup`** — opt-in, config-mutating counterpart to `doctor`.
|
|
163
|
+
Writes the `stata-code` MCP server entry into Claude Code (`.mcp.json`),
|
|
164
|
+
Cursor (`.cursor/mcp.json`), and VS Code (`.vscode/mcp.json`), preserving other
|
|
165
|
+
servers and backing up any file it overwrites (`--dry-run`, `--python`,
|
|
166
|
+
`--json`). Codex (TOML) and Claude Desktop are emitted as copy-paste snippets.
|
|
167
|
+
- **Static do-file linting** (`stata_code.core.lint`), exposed as the `lint_do`
|
|
168
|
+
MCP tool (19th tool) and `stata-code lint`. A Stata-free syntactic check —
|
|
169
|
+
unbalanced braces, a `program` / `mata` / `python` block with no `end`, a stray
|
|
170
|
+
`end`, and a dangling `///` — so an agent can catch a class of mistakes before
|
|
171
|
+
spending a run. Advisory: a clean result is not a correctness guarantee.
|
|
172
|
+
- Public API: `lint_code`, `LintFinding`, `CommandPolicy`, `Violation`,
|
|
173
|
+
`policy_from_env`.
|
|
174
|
+
- **Console (batch) backend** (`stata_code.core.console`, `Backend.CONSOLE`).
|
|
175
|
+
Drives the Stata command-line executable in batch mode and parses the log plus
|
|
176
|
+
a marker-delimited results dump into the same v1.0 `RunResult` — typed
|
|
177
|
+
`r()`/`e()` scalars and macros, the estimation matrices (`e(b)`/`e(V)`/
|
|
178
|
+
`r(table)`), the error taxonomy, warnings, and dataset metadata. Works with
|
|
179
|
+
**Stata 13+** and **no pystata**. Exposed as `stata_code.run_console()`,
|
|
180
|
+
`stata-code run --backend {auto,pystata,console}`, and a `doctor` check. Stateless
|
|
181
|
+
per call; graphs not captured yet. Public API adds `run_console`,
|
|
182
|
+
`console_available`, `find_stata_cli`, `ConsoleNotAvailable`.
|
|
183
|
+
- **Zero-Python standalone binary** — `scripts/build_standalone.py` (PyInstaller)
|
|
184
|
+
and a CI workflow template (`packaging/standalone.github-workflow.yml`, copy to
|
|
185
|
+
`.github/workflows/`) build a self-contained `stata-code` executable per OS.
|
|
186
|
+
Paired with `--backend console` it needs no Python on the target machine.
|
|
187
|
+
- **One-click VS Code onboarding** — when the MCP server is missing, the extension
|
|
188
|
+
offers to create a workspace `.venv` and install the package; also available as
|
|
189
|
+
the command-palette entry "Stata: Set Up MCP Server (create .venv)".
|
|
190
|
+
- **Benchmark harness** (`benchmarks/`) — a tool-agnostic scaffold measuring
|
|
191
|
+
iterations-to-correct and tokens for typed vs. raw-log execution, with an offline
|
|
192
|
+
mock runner wired end-to-end and CI-covered.
|
|
193
|
+
- Chinese quickstart ([docs/quickstart.zh.md](docs/quickstart.zh.md)).
|
|
194
|
+
|
|
195
|
+
## 0.9.1 — 2026-07-22
|
|
196
|
+
|
|
197
|
+
Quality-hardening pass: correctness fixes from an adversarial code review,
|
|
198
|
+
a large offline test expansion, and a documentation accuracy audit. No new
|
|
199
|
+
features, no API or schema changes.
|
|
200
|
+
|
|
201
|
+
A second review round found that three of the first round's fixes were
|
|
202
|
+
incomplete or had introduced a regression of their own; those are the first
|
|
203
|
+
four entries below. The common failure was testing a fix in isolation while
|
|
204
|
+
the real call path stayed uncovered — the new tests go through `SessionPool`
|
|
205
|
+
rather than `WorkerProcess` wherever the pool is what carries the bug, and
|
|
206
|
+
each was confirmed to fail against the pre-fix source.
|
|
207
|
+
|
|
208
|
+
### Fixed
|
|
209
|
+
|
|
210
|
+
- **A read-only status query no longer kills a healthy mid-run worker.**
|
|
211
|
+
Once `send_simple_op` learned to bail out rather than block on the worker
|
|
212
|
+
lock, that bail-out surfaced as a `_WorkerTimeout` — and
|
|
213
|
+
`SessionPool.stata_info` treated "busy" and "dead" identically: it
|
|
214
|
+
SIGTERMed the worker and dropped it from the pool. Calling the
|
|
215
|
+
`stata_info` tool during a long `bootstrap`/`reghdfe` therefore returned
|
|
216
|
+
the user's run as `adapter_crash` and wiped the session's loaded data, and
|
|
217
|
+
because the MCP layer swallows the exception the kill was invisible in the
|
|
218
|
+
tool response. Busy is now a distinct `_WorkerBusy` signal (a
|
|
219
|
+
`_WorkerTimeout` subclass, so warn-only callers are unaffected) that never
|
|
220
|
+
kills.
|
|
221
|
+
- **A timed-out read no longer steals the next request's response.** Each
|
|
222
|
+
request spawned its own `readline()` thread and abandoned it on deadline
|
|
223
|
+
overrun; the orphan stayed blocked on the pipe and consumed the *following*
|
|
224
|
+
response. The next `execute()` then burned its entire timeout (default
|
|
225
|
+
600 s) waiting for a reply that had already been eaten, after which the
|
|
226
|
+
pool killed a perfectly healthy worker. Reads now come off one long-lived
|
|
227
|
+
pump thread per worker, which cannot orphan, and responses carrying a
|
|
228
|
+
stale request id are discarded rather than mis-matched.
|
|
229
|
+
- **`send_simple_op` honors its full timeout budget.** The read deadline was
|
|
230
|
+
computed *after* acquiring the worker lock, so a call that spent most of
|
|
231
|
+
its budget waiting for the lock got a second full budget for the read —
|
|
232
|
+
worst case 2x `timeout_ms`, and `list_session_info_detailed` iterates
|
|
233
|
+
workers serially, so N busy workers stalled the status tool for
|
|
234
|
+
`2 * timeout * N`. The deadline is now fixed up front.
|
|
235
|
+
- **Repeated identical notes are no longer double-counted.** The previous
|
|
236
|
+
de-dup short-circuited before recording the matched span, so the second and
|
|
237
|
+
later copies of an identical `note:` line went unclaimed and were picked up
|
|
238
|
+
again by the generic-note pass — the exact double-count the fix was meant
|
|
239
|
+
to remove, reachable from any `foreach` loop re-running the same
|
|
240
|
+
regression.
|
|
241
|
+
- **`stata_info`'s worker eviction is identity-checked.** It popped the
|
|
242
|
+
session's worker unconditionally, so a concurrent `execute()` that had
|
|
243
|
+
already noticed the death and registered a replacement got *its* worker
|
|
244
|
+
evicted — leaking a live pystata subprocess that `request_cancel` /
|
|
245
|
+
`kill_session` / `shutdown` could no longer reach.
|
|
246
|
+
- **`kill()`'s already-dead fast path is identity-checked** too, matching its
|
|
247
|
+
own slow path: it could otherwise clear a handle a concurrent `execute()`
|
|
248
|
+
had just respawned into, stranding a live subprocess mid-request.
|
|
249
|
+
- **`TypeError` is no longer blanket-classified as a caller error.** The
|
|
250
|
+
guard wrapped the whole `execute()` call, not just argument binding, so a
|
|
251
|
+
`TypeError` raised deep inside result collection told the agent its
|
|
252
|
+
arguments were wrong when nothing was — and routed the failure out of the
|
|
253
|
+
`adapter_crash` path that would have recycled the wedged worker. Malformed
|
|
254
|
+
options are now caught by signature binding before the call; deeper
|
|
255
|
+
`TypeError`s stay in the `worker_error` bucket.
|
|
256
|
+
- **Session ids longer than Stata's 32-character name cap are mapped.** They
|
|
257
|
+
were passed through verbatim as a frame name, so `frame create` failed with
|
|
258
|
+
rc 198; they now route through the hashed mapped-frame path.
|
|
259
|
+
|
|
260
|
+
- **Worker stderr pipe is now drained continuously.** The subprocess pool
|
|
261
|
+
only read a worker's stderr after death, so a worker whose cumulative
|
|
262
|
+
stderr output (pystata banners, Stata C-side messages, warnings) exceeded
|
|
263
|
+
the ~64 KB OS pipe buffer blocked mid-write and never responded — the
|
|
264
|
+
parent then misreported the healthy worker as a `timeout` and killed it
|
|
265
|
+
(or hung forever with `timeout_ms=None`). A per-worker daemon thread now
|
|
266
|
+
drains stderr into a bounded tail buffer, which also enriches crash
|
|
267
|
+
messages.
|
|
268
|
+
- **A worker-reported failure no longer destroys the session.** A live
|
|
269
|
+
worker answering `{ok:false, error_kind:"worker_error"}` was handled like
|
|
270
|
+
a dead process: killed and dropped from the pool, wiping the session's
|
|
271
|
+
loaded dataset and `r()`/`e()` state — reachable from a mere argument
|
|
272
|
+
type typo. Such failures now surface as structured errors while the
|
|
273
|
+
worker (and the session's data) survives; the worker also classifies
|
|
274
|
+
`TypeError` as `invalid_request`.
|
|
275
|
+
- **LRU eviction skips busy workers.** `last_used` is stale for a mid-run
|
|
276
|
+
worker, so capacity pressure from an unrelated new session preferred the
|
|
277
|
+
longest-running request as its eviction victim (SIGTERM mid-run →
|
|
278
|
+
`adapter_crash`, session data lost). Eviction now skips in-flight
|
|
279
|
+
workers, bumps `last_used` at request start, and kills victims outside
|
|
280
|
+
the pool lock; timeout/error cleanup no longer removes a newer worker
|
|
281
|
+
handle registered in the interim.
|
|
282
|
+
- **Status queries no longer hang behind a long run.** `send_simple_op`
|
|
283
|
+
blocked unboundedly on the worker lock held by an in-flight `execute()`;
|
|
284
|
+
lock acquisition now counts against the same timeout budget and surfaces
|
|
285
|
+
as a per-session "busy" warning.
|
|
286
|
+
- **`rc` is taken from the last `r(NNN);` in a failure transcript.** An
|
|
287
|
+
earlier successful command echoing a literal `r(NNN);` (display string,
|
|
288
|
+
help output) previously hijacked the return code — and therefore the
|
|
289
|
+
`ErrorKind` classification, suggestions, and recovery contract.
|
|
290
|
+
- **Indented notes are no longer double-counted as warnings.** An indented
|
|
291
|
+
`note: … omitted because of collinearity` line produced both an
|
|
292
|
+
`omitted_collinear` and a generic `note` warning.
|
|
293
|
+
- **A session literally named `"default"` no longer aliases `"main"`.** In
|
|
294
|
+
in-process mode it silently shared `main`'s Stata frame (one dataset for
|
|
295
|
+
two nominally distinct sessions); it now routes through the private
|
|
296
|
+
mapped-frame path.
|
|
297
|
+
- **Matrices with missing sfi row/col names are no longer dropped.**
|
|
298
|
+
Positional names (`r1…`/`c1…`) are synthesized instead of letting the
|
|
299
|
+
shape validator silently discard successfully read values.
|
|
300
|
+
- **`get_graph(format=…)` is no longer a silent no-op.** Requesting a
|
|
301
|
+
format different from the stored one now returns `invalid_request`
|
|
302
|
+
instead of returning mismatched bytes.
|
|
303
|
+
- **Post-run file snapshots survive escaping symlinks.** A symlink inside
|
|
304
|
+
the working dir pointing outside it crashed `changed_output_files` with
|
|
305
|
+
an uncaught `ValueError`; it is now skipped.
|
|
306
|
+
|
|
307
|
+
### Added
|
|
308
|
+
|
|
309
|
+
- **≈300 new offline tests** (467 → 724 passing without Stata); coverage
|
|
310
|
+
73% → 90% overall (`mcp/server` 74→99%, `kernel` 73→98%, `_pool` 68→96%,
|
|
311
|
+
`_refs` 68→100%, `log_artifacts` 76→96%, `runner` 29→72%). The package is
|
|
312
|
+
now fully mypy-clean, including the previously unchecked top-level
|
|
313
|
+
modules.
|
|
314
|
+
- **Community health files**: `CONTRIBUTING.md` (dev setup + the exact CI
|
|
315
|
+
gates), `CODE_OF_CONDUCT.md` (Contributor Covenant 2.1), `SECURITY.md`,
|
|
316
|
+
and GitHub issue templates that ask for `stata-code doctor` output.
|
|
317
|
+
- **Second-round regression tests** for the eight fixes listed above, each
|
|
318
|
+
verified to fail against the pre-fix source (823 passing locally with
|
|
319
|
+
Stata). They drive real subprocess workers through `SessionPool` — a
|
|
320
|
+
status query racing a live run, a worker that drops its first request, a
|
|
321
|
+
worker that releases the lock partway — rather than asserting on a mocked
|
|
322
|
+
seam, which is what let the first round's regressions through.
|
|
323
|
+
|
|
324
|
+
### Docs
|
|
325
|
+
|
|
326
|
+
- Regenerated the skill's `rc → kind` reference from `errors.py` — it still
|
|
327
|
+
documented the pre-0.9.0 mappings the taxonomy audit had corrected; fixed
|
|
328
|
+
the "32 kinds" claims (the enum has 31).
|
|
329
|
+
- `plugin.json` install verify no longer uses `stata-code-mcp --help`
|
|
330
|
+
(which hangs); it runs `stata-code doctor --no-stata-probe`.
|
|
331
|
+
- PUBLISHING.md / AGENTS.md version-bump lists now cover all eight version
|
|
332
|
+
literals the release gate checks; removed the stale claim that a built
|
|
333
|
+
`.vsix` ships in the repo.
|
|
334
|
+
- SCHEMA.md documents `EstimationResult.command_family` and `.diagnostics`;
|
|
335
|
+
status lines updated v0.8 → v0.9; README.zh.md re-synced with the English
|
|
336
|
+
README (plugin-marketplace install, per-client MCP config table, Open VSX
|
|
337
|
+
and first-activation notes, cell/section conventions).
|
|
338
|
+
|
|
7
339
|
## 0.9.0 — 2026-06-23
|
|
8
340
|
|
|
9
341
|
### Fixed
|
|
@@ -226,8 +558,8 @@ to semver-major.minor for the result schema (see `SCHEMA.md` §6).
|
|
|
226
558
|
|
|
227
559
|
### Added
|
|
228
560
|
|
|
229
|
-
- **Claude Code plugin marketplace manifest.** `.claude-plugin/marketplace.json`
|
|
230
|
-
|
|
561
|
+
- **Claude Code plugin marketplace manifest.** `.claude-plugin/marketplace.json` +
|
|
562
|
+
`.claude-plugin/plugin.json` expose the repo as a single-plugin
|
|
231
563
|
marketplace, so users can install everything (MCP server config + agent
|
|
232
564
|
skill) with `claude plugin marketplace add brycewang-stanford/stata-code`
|
|
233
565
|
followed by `claude plugin install stata-code`.
|
|
@@ -668,6 +1000,9 @@ Aggregated from the prior `Unreleased` section; covers 0.6.1 and 0.6.2.
|
|
|
668
1000
|
`vscode/package.json`, and the VSCode MCP-client handshake all
|
|
669
1001
|
declare `0.3.0`.
|
|
670
1002
|
|
|
1003
|
+
- **MCP server tool count is now 8** (added `get_matrix`,
|
|
1004
|
+
`cancel_session`).
|
|
1005
|
+
|
|
671
1006
|
### Added
|
|
672
1007
|
|
|
673
1008
|
- **VSCode extension v0.3 — full UI surface** (`vscode/`). Beyond the
|
|
@@ -750,11 +1085,6 @@ Aggregated from the prior `Unreleased` section; covers 0.6.1 and 0.6.2.
|
|
|
750
1085
|
in-process and has no clean cancel primitive). Hard interruption
|
|
751
1086
|
remains deferred to the subprocess-based runtime planned for v0.3+.
|
|
752
1087
|
|
|
753
|
-
### Changed
|
|
754
|
-
|
|
755
|
-
- **MCP server tool count is now 8** (added `get_matrix`,
|
|
756
|
-
`cancel_session`).
|
|
757
|
-
|
|
758
1088
|
## [0.2.0] — 2026-05-07
|
|
759
1089
|
|
|
760
1090
|
The first release that actually ships an end-to-end Stata pipeline. The v1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stata-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
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
|
|
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
</p>
|
|
43
43
|
|
|
44
44
|
<p align="center">
|
|
45
|
-
<a href="README.md"><strong>English</strong></a> | <a href="README.
|
|
45
|
+
<a href="README.en.md"><strong>English</strong></a> | <a href="README.md">中文</a>
|
|
46
46
|
</p>
|
|
47
47
|
|
|
48
48
|
# stata-code
|
|
@@ -99,13 +99,13 @@ Then just ask:
|
|
|
99
99
|
|
|
100
100
|
`stata-code` writes the do-file, runs it, returns the table, and interprets the result — and can re-estimate the same ATT with [StatsPAI](https://github.com/brycewang-stanford/StatsPAI) to confirm the two stacks agree. These workflows ship as one-call MCP prompts (`did_event_study`, `iv_2sls`, `rdd`, `publication_table`, `cross_validate_did`) backed by an on-demand [recipe library](skills/stata-code/references/recipes/).
|
|
101
101
|
|
|
102
|
-
**Why `stata-code`:** MIT-licensed · ships as an MCP server, a bundled agent skill, a Jupyter kernel, **and** a
|
|
102
|
+
**Why `stata-code`:** MIT-licensed · ships as an MCP server, a bundled agent skill, a Jupyter kernel, a VS Code extension, **and** a plain-terminal CLI (`stata-code run`) · one structured, token-economy result schema (typed errors, native `r()` / `e()`) · runs on **Stata 17+ via pystata *or* Stata 13+ via a console backend that needs no pystata** · a zero-Python standalone binary · a default command-safety guard for unattended agents · cross-stack validation with StatsPAI for the Cunningham check.
|
|
103
103
|
|
|
104
104
|
```text
|
|
105
105
|
┌────────────────────────────────────────┐
|
|
106
106
|
│ stata-code core (Python) │
|
|
107
107
|
│ │
|
|
108
|
-
│ • pystata
|
|
108
|
+
│ • pystata 17+ / console 13+ backends │
|
|
109
109
|
│ • v1.0 unified result schema │
|
|
110
110
|
│ • token-economy defaults │
|
|
111
111
|
│ • multi-session via Stata frames │
|
|
@@ -118,10 +118,13 @@ Then just ask:
|
|
|
118
118
|
└─────────────┘ └────────────┘ └─────────────────┘
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
**
|
|
121
|
+
A fourth frontend, a **plain-terminal CLI** (`stata-code run` / `lint` / `setup`), gives any agent that can shell out — or a bare terminal — the same typed `RunResult`. And the core runs two backends: **pystata** (Stata 17+, in-memory sessions) or a **console backend** (Stata 13+ in batch mode, no pystata) — both returning the identical schema.
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
**Status: v0.10 (July 2026)** — the core, MCP server, Jupyter kernel, VS Code extension, and CLI work end-to-end against Stata 18 MP; the console backend broadens coverage to Stata 13+ without pystata. The test suite covers schema, runner, console parser, MCP, kernel, notebook, run-index, subprocess-pool, command policy, linter, and VS Code modules; CI also checks linting, type safety, schema generation, package metadata, and VSIX packaging. License: **MIT**.
|
|
124
124
|
|
|
125
|
+
Four workflows the current tree explicitly supports for end users and agents:
|
|
126
|
+
|
|
127
|
+
- **Run Stata from a plain terminal or a zero-Python binary.** `stata-code run analysis.do` (or `-e "code"`, or stdin) prints the same structured `RunResult` — text or `--json` — so any agent that can shell out gets the typed error loop without MCP. `--backend console` runs **Stata 13+ with no pystata**, and a standalone binary needs no Python install at all. See [From the Command Line](#from-the-command-line-bash).
|
|
125
128
|
- **Run Stata code from a Jupyter notebook.** `pip install "stata-code[kernel]"` + `stata-code-kernel install --user` registers a **Stata** kernel that the Jupyter Notebook UI, JupyterLab, and the VS Code Jupyter extension all pick up by name. Cells render Stata logs, graphs, and warnings inline (the kernel logo bundled since v0.5 makes it appear in VS Code's kernel picker too). See [As a Jupyter Kernel](#as-a-jupyter-kernel).
|
|
126
129
|
- **Optional agent "fix and rerun" loop.** `stata_run` returns typed `error.kind/line/context` plus `suggestions` on every failure. By default Claude Code only reports diagnostics — but if you explicitly say "fix this and rerun until it passes", the agent uses the same fields to edit your `.do` file and re-call `stata_run` until the run is green. The repair loop is **opt-in**: failed runs are diagnostics first, not automatic rewrite permission. See [Error Recovery in Agent Workflows](#error-recovery-in-agent-workflows).
|
|
127
130
|
- **Economist workflow guides.** The bundled skill and cookbook now cover
|
|
@@ -222,6 +225,72 @@ else:
|
|
|
222
225
|
print("hint:", s.action) # "Did you mean `mpg`?"
|
|
223
226
|
```
|
|
224
227
|
|
|
228
|
+
### From the Command Line (Bash)
|
|
229
|
+
|
|
230
|
+
Any agent or script that can shell out gets the same structured engine — no MCP
|
|
231
|
+
required. `stata-code run` executes a `.do` file, one or more `-e` snippets, or
|
|
232
|
+
code piped on stdin, and prints the `RunResult`:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
stata-code run analysis.do # run a do-file, text summary
|
|
236
|
+
stata-code run -e "sysuse auto" -e "regress mpg weight"
|
|
237
|
+
stata-code run analysis.do --json # full RunResult JSON (for agents)
|
|
238
|
+
echo "summarize price" | stata-code run - # read code from stdin
|
|
239
|
+
stata-code run model.do --graphs out/ # also export graphs to out/
|
|
240
|
+
stata-code run job.do --session modelA --timeout-ms 120000
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Exit code is `0` on success and `1` on a Stata / adapter error, so it drops into
|
|
244
|
+
CI and scripted fix-and-rerun loops. `stata-code lint analysis.do` runs the
|
|
245
|
+
static checker (unbalanced braces, missing `end`, dangling `///`) without
|
|
246
|
+
touching Stata.
|
|
247
|
+
|
|
248
|
+
**Backends — Stata 13+ without pystata.** `--backend` selects how code runs:
|
|
249
|
+
`pystata` (Stata 17+, in-memory sessions), `console` (Stata 13+ batch, no pystata,
|
|
250
|
+
stateless), or `auto` (default: pystata when available, else console). The console
|
|
251
|
+
backend drives the Stata command-line executable and parses the log into the same
|
|
252
|
+
typed `RunResult` — typed `r()`/`e()`, the estimation table, and the error
|
|
253
|
+
taxonomy — so older Stata and pystata-free environments are first-class:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
stata-code run analysis.do --backend console
|
|
257
|
+
export STATA_CODE_STATA_CLI=/usr/local/stata18/stata-mp # if not auto-found
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Zero-Python binary.** A standalone `stata-code` executable (built by
|
|
261
|
+
[`scripts/build_standalone.py`](scripts/build_standalone.py), with a ready-to-use
|
|
262
|
+
CI workflow template at
|
|
263
|
+
[`packaging/standalone.github-workflow.yml`](packaging/standalone.github-workflow.yml))
|
|
264
|
+
bundles the runtime and needs no Python install. Paired with `--backend console`,
|
|
265
|
+
it is a fully Python-free path to typed Stata results.
|
|
266
|
+
|
|
267
|
+
### One-Command Client Setup
|
|
268
|
+
|
|
269
|
+
`stata-code setup` writes the MCP server entry into a client's config — the
|
|
270
|
+
opt-in, mutating counterpart to the read-only `doctor`. It preserves other
|
|
271
|
+
servers and backs up any file it overwrites:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
stata-code setup --all # Claude Code, Cursor, VS Code (project)
|
|
275
|
+
stata-code setup --claude --dry-run # preview without writing
|
|
276
|
+
stata-code setup --vscode --python .venv/bin/python # pin an interpreter
|
|
277
|
+
stata-code setup --codex # print a copy-paste TOML snippet
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Command Safety
|
|
281
|
+
|
|
282
|
+
By default the runner blocks OS-escape and file-deletion commands (`shell`,
|
|
283
|
+
`winexec`, `erase`, `rm`, `rmdir`, and the `!` shell escape) *before* they reach
|
|
284
|
+
Stata, so an autonomous agent loop can't delete files or run arbitrary shell
|
|
285
|
+
commands. A block returns a `policy_blocked` result (`rc=-4`) rather than
|
|
286
|
+
running. It is a guard rail, not a sandbox; tune it with environment variables:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
STATA_CODE_COMMAND_POLICY=off # disable the guard entirely
|
|
290
|
+
STATA_CODE_POLICY_ALLOW=shell # allow specific commands (comma-separated)
|
|
291
|
+
STATA_CODE_POLICY_BLOCK=python # block additional commands
|
|
292
|
+
```
|
|
293
|
+
|
|
225
294
|
### As an MCP Server
|
|
226
295
|
|
|
227
296
|
After `pip install "stata-code[mcp]"`, the `stata-code-mcp` binary is on your `PATH`. You can wire it into Claude Code, Cursor, Claude Desktop, or any other MCP-compatible client.
|
|
@@ -243,7 +312,7 @@ claude mcp add stata-code --scope local -- stata-code-mcp
|
|
|
243
312
|
claude mcp add stata-code --scope project -- stata-code-mcp
|
|
244
313
|
```
|
|
245
314
|
|
|
246
|
-
Then launch `claude` and type `/mcp` to confirm `stata-code` shows up with its
|
|
315
|
+
Then launch `claude` and type `/mcp` to confirm `stata-code` shows up with its 21 tools (`stata_run`, `stata_run_status`, `list_background_runs`, `stata_info`, `get_log`, `search_log`, `get_graph`, `get_matrix`, `inspect_data`, `lint_do`, `install_package`, `list_sessions`, `cancel_session`, `reset_session`, `notebook_outline`, `notebook_get_cell`, `notebook_locate`, `notebook_edit_cell`, `notebook_insert_cell`, `notebook_delete_cell`, `list_runs`).
|
|
247
316
|
|
|
248
317
|
#### Error Recovery in Agent Workflows
|
|
249
318
|
|
|
@@ -331,17 +400,20 @@ If an OpenAI-backed client reports `API Error: 400 Invalid schema for function
|
|
|
331
400
|
upgrade to `stata-code>=0.6.5`, then restart the MCP client. Older server
|
|
332
401
|
processes keep advertising the stale schema until they are restarted.
|
|
333
402
|
|
|
334
|
-
The MCP server registers
|
|
403
|
+
The MCP server registers 21 tools:
|
|
335
404
|
|
|
336
405
|
| Tool | Purpose |
|
|
337
406
|
| --- | --- |
|
|
338
|
-
| `stata_run` | Execute Stata code and return a v1.0 RunResult JSON |
|
|
407
|
+
| `stata_run` | Execute Stata code and return a v1.0 RunResult JSON; `include_results` / `include_estimation` bound the payload, `timeout_ms` enforces a hard deadline, `run_in_background` returns a job id |
|
|
408
|
+
| `stata_run_status` | Poll a background run's status and result; `wait_ms` blocks up to a bounded time |
|
|
409
|
+
| `list_background_runs` | List background runs tracked by this server |
|
|
339
410
|
| `stata_info` | Report Stata edition, version, and capabilities |
|
|
340
411
|
| `get_log` | Fetch the full log behind a `log://` ref |
|
|
341
412
|
| `search_log` | Search matching lines inside a stored `log://` payload |
|
|
342
413
|
| `get_graph` | Fetch graph bytes behind a `graph://` ref (`ImageContent`) |
|
|
343
414
|
| `get_matrix` | Fetch matrix payloads behind a `matrix://` ref |
|
|
344
415
|
| `inspect_data` | Run `describe` + `codebook` and return compact dataset metadata |
|
|
416
|
+
| `lint_do` | Statically check do-file source (unbalanced braces, missing `end`, dangling `///`) before spending a run |
|
|
345
417
|
| `install_package` | Install an SSC or explicit `net install` package and verify it resolves |
|
|
346
418
|
| `list_sessions` | Enumerate live sessions |
|
|
347
419
|
| `cancel_session` | Cancel a session; the subprocess-backed path terminates in-flight runs and short-circuits pending ones |
|
|
@@ -485,7 +557,9 @@ stata_code/
|
|
|
485
557
|
│ ├── runner.py # in-process execute(); collects everything via sfi
|
|
486
558
|
│ └── _pool.py # subprocess workers for public API / MCP hard timeouts
|
|
487
559
|
├── mcp/
|
|
488
|
-
│
|
|
560
|
+
│ ├── server.py # MCP server (21 tools)
|
|
561
|
+
│ └── ...
|
|
562
|
+
├── core/console.py # console (batch) backend — Stata 13+, no pystata
|
|
489
563
|
└── kernel/
|
|
490
564
|
└── kernel.py # Jupyter kernel
|
|
491
565
|
```
|
|
@@ -496,18 +570,33 @@ stata_code/
|
|
|
496
570
|
|
|
497
571
|
## Comparison
|
|
498
572
|
|
|
499
|
-
| | stata-code | SepineTam/stata-mcp | hanlulong/stata-mcp | nbstata |
|
|
500
|
-
| --- | --- | --- | --- | --- |
|
|
501
|
-
| License | **MIT** | AGPL-3.0 | MIT | GPL-3.0 |
|
|
502
|
-
| Standalone MCP | ✓ | ✓ | bundled with VS Code | — |
|
|
503
|
-
| Jupyter kernel | ✓ | — | — | ✓ |
|
|
504
|
-
| Unified result schema | ✓ ([SCHEMA.md](SCHEMA.md)) | per-tool | per-tool | per-tool |
|
|
505
|
-
|
|
|
506
|
-
|
|
|
507
|
-
|
|
|
508
|
-
|
|
|
509
|
-
|
|
510
|
-
|
|
573
|
+
| | stata-code | SepineTam/stata-mcp | hanlulong/stata-mcp | stata-all-in-one | nbstata |
|
|
574
|
+
| --- | --- | --- | --- | --- | --- |
|
|
575
|
+
| License | **MIT** | AGPL-3.0 | MIT | MIT | GPL-3.0 |
|
|
576
|
+
| Standalone MCP | ✓ | ✓ | bundled with VS Code | — (bespoke HTTP + copy-paste) | — |
|
|
577
|
+
| Jupyter kernel | ✓ | — | — | — | ✓ |
|
|
578
|
+
| Unified result schema | ✓ ([SCHEMA.md](SCHEMA.md)) | per-tool | per-tool | raw log to the agent | per-tool |
|
|
579
|
+
| Typed errors + suggestions | ✓ (32 kinds) | — | — | — | — |
|
|
580
|
+
| Token-economy defaults | ✓ (log refs, graph refs) | — | — | — | — |
|
|
581
|
+
| Command-safety guard | ✓ (`shell`/`erase`/`rmdir`/`!`) | ✓ (27-rule) | — | — | — |
|
|
582
|
+
| Bash / plain-terminal CLI | ✓ (`stata-code run`) | ✓ | — | — | — |
|
|
583
|
+
| One-command client setup | ✓ (`stata-code setup`) | ✓ (`install --all`) | bundled | — | — |
|
|
584
|
+
| Static pre-run lint | ✓ (`lint_do`) | — | — | — | — |
|
|
585
|
+
| Stata 13–16 (no pystata) | ✓ (console backend) | ✓ | — | ✓ (COM/dylib) | — |
|
|
586
|
+
| Zero-Python install | ✓ (standalone binary) | — | — | ✓ (VS Code-native) | — |
|
|
587
|
+
| Human IDE polish (data viewer, inline graphs) | growing | — | ✓ | ✓ (strongest) | ✓ |
|
|
588
|
+
| Multi-session | ✓ (Stata frames) | partial | — | — | — |
|
|
589
|
+
| Mature ecosystem | early | ✓ (statamcp.com) | ✓ (11k installs) | ✓ (distributor-backed) | ✓ |
|
|
590
|
+
|
|
591
|
+
`stata-code` owns the **agent-native, typed-contract** lane: one structured
|
|
592
|
+
`RunResult` schema across MCP, Jupyter, VS Code, and a plain-terminal CLI; a
|
|
593
|
+
32-kind error taxonomy with recovery contracts; token-economy refs; and now a
|
|
594
|
+
console backend (Stata 13+) plus a zero-Python binary. Editor-first tools like
|
|
595
|
+
`stata-all-in-one` lead on human IDE polish and hand the agent raw log text;
|
|
596
|
+
`stata-code` matches their onboarding/version reach while keeping the typed
|
|
597
|
+
execution contract they don't have. See
|
|
598
|
+
[docs/competitive-landscape.md](docs/competitive-landscape.md) for the full
|
|
599
|
+
teardown.
|
|
511
600
|
|
|
512
601
|
---
|
|
513
602
|
|
|
@@ -522,8 +611,13 @@ stata_code/
|
|
|
522
611
|
- Graph capture: `png` / `svg` / `pdf` with ref store and source-command attribution
|
|
523
612
|
- Log truncation with ref store
|
|
524
613
|
- Warning extraction: 5 categories + generic notes
|
|
525
|
-
-
|
|
526
|
-
- MCP server:
|
|
614
|
+
- 32-kind error taxonomy with canonical suggestions
|
|
615
|
+
- MCP server: 21 tools, including notebook navigation / search / atomic edits, the run-bundle index (`list_runs`), log grep (`search_log`), dataset inspection (`inspect_data`), static linting (`lint_do`), and package installation (`install_package`)
|
|
616
|
+
- Command-safety guard: OS-escape / file-deletion commands (`shell`, `winexec`, `erase`, `rm`, `rmdir`, `!`) are blocked before Stata runs; configurable via `STATA_CODE_COMMAND_POLICY` / `STATA_CODE_POLICY_ALLOW` / `STATA_CODE_POLICY_BLOCK`
|
|
617
|
+
- Bash / plain-terminal surface: `stata-code run` (a `.do` file, `-e` snippets, or stdin) prints the same structured `RunResult` any agent that can shell out can consume; `stata-code lint` runs the linter; `stata-code setup` writes MCP client configs
|
|
618
|
+
- Console (batch) backend (`core/console.py`, `--backend console`, `run_console()`): drives the Stata command-line executable, parses the log into the same typed `RunResult`, and supports **Stata 13+ with no pystata**
|
|
619
|
+
- Zero-Python standalone binary ([`scripts/build_standalone.py`](scripts/build_standalone.py) + CI workflow template [`packaging/standalone.github-workflow.yml`](packaging/standalone.github-workflow.yml)); with `--backend console` it is a fully Python-free path to typed results
|
|
620
|
+
- One-click VS Code onboarding: the extension offers to create a workspace `.venv` and install the server (command palette: “Stata: Set Up MCP Server”)
|
|
527
621
|
- Jupyter kernel: rewired to the v1.0 pipeline, kernel logos bundled
|
|
528
622
|
- Matrix size cap + `get_matrix(ref)` for large matrices (>10k cells)
|
|
529
623
|
- Subprocess-backed hard timeout and cancellation for the public Python API and MCP server: `timeout_ms`, `cancel(session_id)`, and MCP `cancel_session`
|
|
@@ -541,12 +635,16 @@ stata_code/
|
|
|
541
635
|
|
|
542
636
|
### Next Up
|
|
543
637
|
|
|
544
|
-
-
|
|
545
|
-
-
|
|
546
|
-
-
|
|
638
|
+
- Streaming / progress for long runs (`log.complete:false`, incremental log lines) so 20-minute `boottest` / `csdid` jobs report before they finish
|
|
639
|
+
- Hard timeout / cancellation for the Jupyter kernel (move it from the direct in-process runner to the subprocess pool, or an equivalent)
|
|
640
|
+
- Console backend: graph capture and richer matrix coverage (values currently materialized for the estimation matrices; state is per-call)
|
|
641
|
+
- Human IDE polish to match editor-first tools: inline graph rendering + DPI export, a scalable data viewer, and an optional "attach to a running Stata" backend
|
|
642
|
+
- Publish the reliability/token [benchmark](benchmarks/) results (typed contract vs. raw-log tools) as evidence, not a claim
|
|
547
643
|
- **v1.0** — Stable schema, broader Stata edition coverage
|
|
548
644
|
|
|
549
|
-
See [
|
|
645
|
+
See [docs/competitive-landscape.md](docs/competitive-landscape.md) for how these
|
|
646
|
+
priorities line up against comparable tools, and [SCHEMA.md §7](SCHEMA.md) for
|
|
647
|
+
explicitly out-of-scope items.
|
|
550
648
|
|
|
551
649
|
---
|
|
552
650
|
|
|
@@ -565,10 +663,17 @@ The `stata_required` marker tags the real-Stata integration tests. CI uses `pyte
|
|
|
565
663
|
|
|
566
664
|
## Contributing
|
|
567
665
|
|
|
666
|
+
Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for the
|
|
667
|
+
development setup, the checks CI runs, and PR guidelines. In short:
|
|
668
|
+
|
|
568
669
|
- Read [LICENSE-POLICY.md](LICENSE-POLICY.md) before opening a PR.
|
|
569
670
|
- Add a one-line acknowledgement to your first PR description; the template is in the policy file.
|
|
570
671
|
- Tests are required for any new schema field or runner behavior.
|
|
571
672
|
|
|
673
|
+
Bug reports and support questions go through
|
|
674
|
+
[GitHub issues](https://github.com/brycewang-stanford/stata-code/issues);
|
|
675
|
+
security reports go through [SECURITY.md](SECURITY.md).
|
|
676
|
+
|
|
572
677
|
---
|
|
573
678
|
|
|
574
679
|
## License
|