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.
Files changed (100) hide show
  1. {stata_code-0.9.0 → stata_code-0.11.0}/.gitignore +2 -0
  2. {stata_code-0.9.0 → stata_code-0.11.0}/CHANGELOG.md +337 -7
  3. {stata_code-0.9.0 → stata_code-0.11.0}/PKG-INFO +133 -28
  4. {stata_code-0.9.0 → stata_code-0.11.0}/PUBLISHING.md +16 -7
  5. stata_code-0.9.0/README.md → stata_code-0.11.0/README.en.md +132 -27
  6. stata_code-0.11.0/README.md +567 -0
  7. {stata_code-0.9.0 → stata_code-0.11.0}/SCHEMA.md +119 -32
  8. {stata_code-0.9.0 → stata_code-0.11.0}/docs/competitive-landscape.md +60 -2
  9. {stata_code-0.9.0 → stata_code-0.11.0}/docs/industry-leader-roadmap.md +1 -1
  10. stata_code-0.11.0/docs/quickstart.zh.md +102 -0
  11. {stata_code-0.9.0 → stata_code-0.11.0}/pyproject.toml +5 -2
  12. {stata_code-0.9.0 → stata_code-0.11.0}/schema/run_result.schema.json +88 -0
  13. stata_code-0.11.0/scripts/build_standalone.py +81 -0
  14. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/__init__.py +77 -1
  15. stata_code-0.11.0/stata_code/cli.py +538 -0
  16. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/_pool.py +432 -93
  17. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/_runtime.py +13 -2
  18. stata_code-0.11.0/stata_code/core/console.py +818 -0
  19. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/errors.py +82 -0
  20. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/estimation.py +132 -36
  21. stata_code-0.11.0/stata_code/core/jobs.py +200 -0
  22. stata_code-0.11.0/stata_code/core/lint.py +246 -0
  23. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/log_artifacts.py +62 -2
  24. stata_code-0.11.0/stata_code/core/policy.py +375 -0
  25. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/runner.py +695 -99
  26. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/schema.py +61 -0
  27. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/doctor.py +22 -0
  28. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/kernel/kernel.py +12 -3
  29. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/mcp/server.py +472 -17
  30. stata_code-0.11.0/stata_code/mcp_setup.py +174 -0
  31. stata_code-0.11.0/tests/test_agent_ergonomics.py +867 -0
  32. stata_code-0.11.0/tests/test_benchmark.py +59 -0
  33. stata_code-0.11.0/tests/test_bugfix_regressions.py +556 -0
  34. stata_code-0.11.0/tests/test_cli.py +213 -0
  35. stata_code-0.11.0/tests/test_console.py +304 -0
  36. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_errors.py +5 -1
  37. stata_code-0.11.0/tests/test_lint.py +89 -0
  38. stata_code-0.11.0/tests/test_lint_mcp.py +67 -0
  39. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_mcp.py +82 -134
  40. stata_code-0.11.0/tests/test_mcp_kernel_extra.py +1087 -0
  41. stata_code-0.11.0/tests/test_mcp_transport.py +51 -0
  42. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_new_tools.py +8 -6
  43. stata_code-0.11.0/tests/test_policy.py +156 -0
  44. stata_code-0.11.0/tests/test_pool_refs_extra.py +993 -0
  45. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_runner.py +141 -5
  46. stata_code-0.11.0/tests/test_runner_helpers.py +1213 -0
  47. stata_code-0.9.0/stata_code/cli.py +0 -85
  48. stata_code-0.9.0/tests/test_mcp_stdio.py +0 -163
  49. {stata_code-0.9.0 → stata_code-0.11.0}/LICENSE +0 -0
  50. {stata_code-0.9.0 → stata_code-0.11.0}/LICENSE-POLICY.md +0 -0
  51. {stata_code-0.9.0 → stata_code-0.11.0}/docs/design/hard_timeout.md +0 -0
  52. {stata_code-0.9.0 → stata_code-0.11.0}/examples/01-basic-regression.md +0 -0
  53. {stata_code-0.9.0 → stata_code-0.11.0}/examples/02-did-card-krueger.md +0 -0
  54. {stata_code-0.9.0 → stata_code-0.11.0}/examples/03-graphs.md +0 -0
  55. {stata_code-0.9.0 → stata_code-0.11.0}/examples/04-multi-session.md +0 -0
  56. {stata_code-0.9.0 → stata_code-0.11.0}/examples/05-large-matrix.md +0 -0
  57. {stata_code-0.9.0 → stata_code-0.11.0}/examples/06-cross-stack-parity-audit.md +0 -0
  58. {stata_code-0.9.0 → stata_code-0.11.0}/examples/07-data-mcp-handoff.md +0 -0
  59. {stata_code-0.9.0 → stata_code-0.11.0}/examples/README.md +0 -0
  60. {stata_code-0.9.0 → stata_code-0.11.0}/scripts/build_skill_zip.py +0 -0
  61. {stata_code-0.9.0 → stata_code-0.11.0}/scripts/check_github_actions.py +0 -0
  62. {stata_code-0.9.0 → stata_code-0.11.0}/scripts/check_versions.py +0 -0
  63. {stata_code-0.9.0 → stata_code-0.11.0}/scripts/export_schema.py +0 -0
  64. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/__main__.py +0 -0
  65. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/__init__.py +0 -0
  66. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/_refs.py +0 -0
  67. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/handoff.py +0 -0
  68. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/notebook.py +0 -0
  69. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/provenance.py +0 -0
  70. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/core/run_index.py +0 -0
  71. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/kernel/__init__.py +0 -0
  72. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/kernel/__main__.py +0 -0
  73. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/kernel/assets/logo-32x32.png +0 -0
  74. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/kernel/assets/logo-64x64.png +0 -0
  75. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/kernel/assets/logo-svg.svg +0 -0
  76. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/mcp/__init__.py +0 -0
  77. {stata_code-0.9.0 → stata_code-0.11.0}/stata_code/mcp/__main__.py +0 -0
  78. {stata_code-0.9.0 → stata_code-0.11.0}/tests/__init__.py +0 -0
  79. {stata_code-0.9.0 → stata_code-0.11.0}/tests/conftest.py +0 -0
  80. {stata_code-0.9.0 → stata_code-0.11.0}/tests/fixtures/.gitkeep +0 -0
  81. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_cancel.py +0 -0
  82. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_doctor.py +0 -0
  83. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_estimation.py +0 -0
  84. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_github_actions.py +0 -0
  85. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_handoff.py +0 -0
  86. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_kernel.py +0 -0
  87. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_log_artifacts.py +0 -0
  88. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_method_prompts.py +0 -0
  89. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_notebook.py +0 -0
  90. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_notebook_phase2.py +0 -0
  91. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_pool.py +0 -0
  92. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_provenance.py +0 -0
  93. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_public_api.py +0 -0
  94. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_real_stata.py +0 -0
  95. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_release_versions.py +0 -0
  96. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_run_index.py +0 -0
  97. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_runtime_discovery.py +0 -0
  98. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_schema.py +0 -0
  99. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_schema_artifact.py +0 -0
  100. {stata_code-0.9.0 → stata_code-0.11.0}/tests/test_skill_package.py +0 -0
@@ -238,3 +238,5 @@ stata_code_test_export.png
238
238
 
239
239
  # Demo / scratch notebooks (real tests live under tests/)
240
240
  demo-tests/*.ipynb
241
+
242
+ 社媒文件/
@@ -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
- + `.claude-plugin/plugin.json` expose the repo as a single-plugin
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.9.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.zh.md">中文</a>
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 VS Code extension · one structured, token-economy result schema (typed errors, native `r()` / `e()`) · cross-stack validation with StatsPAI for the Cunningham check.
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 adapter (Stata 17+)
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
- **Status: v0.8 (June 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**.
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
- Three workflows the current tree explicitly supports for end users and agents:
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 18 tools (`stata_run`, `stata_info`, `get_log`, `search_log`, `get_graph`, `get_matrix`, `inspect_data`, `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`).
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 18 tools:
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
- └── server.py # MCP server (18 tools)
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
- | Token-economy defaults | ✓ (log refs, graph refs) | — | — | — |
506
- | Typed errors + suggestions | ✓ (31 kinds) | — | — | — |
507
- | Multi-session | ✓ (Stata frames) | partial | — | — |
508
- | Mature ecosystem | early | ✓ (statamcp.com, cookbook) | ✓ (11k installs) | |
509
-
510
- `stata-code` is the younger, MIT-licensed, agent-native alternative in this problem space. Among the AGPL options, SepineTam's `stata-mcp` is currently more mature; `stata-code` is aimed at cases where copyleft contagion is unacceptable and agents need structured results.
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
- - 31-kind error taxonomy with canonical suggestions
526
- - MCP server: 18 tools, including notebook navigation / search / atomic edits, the run-bundle index (`list_runs`), log grep (`search_log`), dataset inspection (`inspect_data`), and package installation (`install_package`)
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
- - Console fallback for Stata 11–16, re-implemented against the v1.0 schema
545
- - 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
546
- - Extra VS Code polish: extension-host end-to-end tests, first-run diagnostics, and command palette UX
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 [SCHEMA.md §7](SCHEMA.md) for explicitly out-of-scope items.
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