stata-code 0.9.1__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.1 → stata_code-0.11.0}/CHANGELOG.md +186 -0
  2. {stata_code-0.9.1 → stata_code-0.11.0}/PKG-INFO +126 -28
  3. stata_code-0.9.1/README.md → stata_code-0.11.0/README.en.md +125 -27
  4. stata_code-0.11.0/README.md +567 -0
  5. {stata_code-0.9.1 → stata_code-0.11.0}/SCHEMA.md +118 -33
  6. {stata_code-0.9.1 → stata_code-0.11.0}/docs/competitive-landscape.md +58 -0
  7. {stata_code-0.9.1 → stata_code-0.11.0}/docs/industry-leader-roadmap.md +1 -1
  8. stata_code-0.11.0/docs/quickstart.zh.md +102 -0
  9. {stata_code-0.9.1 → stata_code-0.11.0}/pyproject.toml +5 -2
  10. {stata_code-0.9.1 → stata_code-0.11.0}/schema/run_result.schema.json +88 -0
  11. stata_code-0.11.0/scripts/build_standalone.py +81 -0
  12. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/__init__.py +77 -1
  13. stata_code-0.11.0/stata_code/cli.py +538 -0
  14. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/core/_pool.py +109 -9
  15. stata_code-0.11.0/stata_code/core/console.py +818 -0
  16. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/core/errors.py +82 -0
  17. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/core/estimation.py +132 -36
  18. stata_code-0.11.0/stata_code/core/jobs.py +200 -0
  19. stata_code-0.11.0/stata_code/core/lint.py +246 -0
  20. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/core/log_artifacts.py +59 -1
  21. stata_code-0.11.0/stata_code/core/policy.py +375 -0
  22. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/core/runner.py +651 -93
  23. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/core/schema.py +61 -0
  24. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/doctor.py +22 -0
  25. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/kernel/kernel.py +6 -0
  26. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/mcp/server.py +468 -16
  27. stata_code-0.11.0/stata_code/mcp_setup.py +174 -0
  28. stata_code-0.11.0/tests/test_agent_ergonomics.py +867 -0
  29. stata_code-0.11.0/tests/test_benchmark.py +59 -0
  30. stata_code-0.11.0/tests/test_cli.py +213 -0
  31. stata_code-0.11.0/tests/test_console.py +304 -0
  32. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_errors.py +5 -1
  33. stata_code-0.11.0/tests/test_lint.py +89 -0
  34. stata_code-0.11.0/tests/test_lint_mcp.py +67 -0
  35. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_mcp.py +82 -134
  36. stata_code-0.11.0/tests/test_mcp_transport.py +51 -0
  37. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_new_tools.py +8 -6
  38. stata_code-0.11.0/tests/test_policy.py +156 -0
  39. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_runner.py +141 -5
  40. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_runner_helpers.py +109 -17
  41. stata_code-0.9.1/stata_code/cli.py +0 -85
  42. stata_code-0.9.1/tests/test_mcp_stdio.py +0 -163
  43. {stata_code-0.9.1 → stata_code-0.11.0}/.gitignore +0 -0
  44. {stata_code-0.9.1 → stata_code-0.11.0}/LICENSE +0 -0
  45. {stata_code-0.9.1 → stata_code-0.11.0}/LICENSE-POLICY.md +0 -0
  46. {stata_code-0.9.1 → stata_code-0.11.0}/PUBLISHING.md +0 -0
  47. {stata_code-0.9.1 → stata_code-0.11.0}/docs/design/hard_timeout.md +0 -0
  48. {stata_code-0.9.1 → stata_code-0.11.0}/examples/01-basic-regression.md +0 -0
  49. {stata_code-0.9.1 → stata_code-0.11.0}/examples/02-did-card-krueger.md +0 -0
  50. {stata_code-0.9.1 → stata_code-0.11.0}/examples/03-graphs.md +0 -0
  51. {stata_code-0.9.1 → stata_code-0.11.0}/examples/04-multi-session.md +0 -0
  52. {stata_code-0.9.1 → stata_code-0.11.0}/examples/05-large-matrix.md +0 -0
  53. {stata_code-0.9.1 → stata_code-0.11.0}/examples/06-cross-stack-parity-audit.md +0 -0
  54. {stata_code-0.9.1 → stata_code-0.11.0}/examples/07-data-mcp-handoff.md +0 -0
  55. {stata_code-0.9.1 → stata_code-0.11.0}/examples/README.md +0 -0
  56. {stata_code-0.9.1 → stata_code-0.11.0}/scripts/build_skill_zip.py +0 -0
  57. {stata_code-0.9.1 → stata_code-0.11.0}/scripts/check_github_actions.py +0 -0
  58. {stata_code-0.9.1 → stata_code-0.11.0}/scripts/check_versions.py +0 -0
  59. {stata_code-0.9.1 → stata_code-0.11.0}/scripts/export_schema.py +0 -0
  60. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/__main__.py +0 -0
  61. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/core/__init__.py +0 -0
  62. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/core/_refs.py +0 -0
  63. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/core/_runtime.py +0 -0
  64. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/core/handoff.py +0 -0
  65. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/core/notebook.py +0 -0
  66. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/core/provenance.py +0 -0
  67. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/core/run_index.py +0 -0
  68. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/kernel/__init__.py +0 -0
  69. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/kernel/__main__.py +0 -0
  70. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/kernel/assets/logo-32x32.png +0 -0
  71. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/kernel/assets/logo-64x64.png +0 -0
  72. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/kernel/assets/logo-svg.svg +0 -0
  73. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/mcp/__init__.py +0 -0
  74. {stata_code-0.9.1 → stata_code-0.11.0}/stata_code/mcp/__main__.py +0 -0
  75. {stata_code-0.9.1 → stata_code-0.11.0}/tests/__init__.py +0 -0
  76. {stata_code-0.9.1 → stata_code-0.11.0}/tests/conftest.py +0 -0
  77. {stata_code-0.9.1 → stata_code-0.11.0}/tests/fixtures/.gitkeep +0 -0
  78. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_bugfix_regressions.py +0 -0
  79. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_cancel.py +0 -0
  80. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_doctor.py +0 -0
  81. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_estimation.py +0 -0
  82. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_github_actions.py +0 -0
  83. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_handoff.py +0 -0
  84. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_kernel.py +0 -0
  85. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_log_artifacts.py +0 -0
  86. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_mcp_kernel_extra.py +0 -0
  87. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_method_prompts.py +0 -0
  88. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_notebook.py +0 -0
  89. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_notebook_phase2.py +0 -0
  90. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_pool.py +0 -0
  91. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_pool_refs_extra.py +0 -0
  92. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_provenance.py +0 -0
  93. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_public_api.py +0 -0
  94. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_real_stata.py +0 -0
  95. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_release_versions.py +0 -0
  96. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_run_index.py +0 -0
  97. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_runtime_discovery.py +0 -0
  98. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_schema.py +0 -0
  99. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_schema_artifact.py +0 -0
  100. {stata_code-0.9.1 → stata_code-0.11.0}/tests/test_skill_package.py +0 -0
@@ -6,6 +6,192 @@ to semver-major.minor for the result schema (see `SCHEMA.md` §6).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## 0.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
+
9
195
  ## 0.9.1 — 2026-07-22
10
196
 
11
197
  Quality-hardening pass: correctness fixes from an adversarial code review,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stata-code
3
- Version: 0.9.1
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.9 (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