stata-code 0.11.0__tar.gz → 0.12.1__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.11.0 → stata_code-0.12.1}/CHANGELOG.md +97 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/PKG-INFO +126 -16
- {stata_code-0.11.0 → stata_code-0.12.1}/README.en.md +123 -13
- {stata_code-0.11.0 → stata_code-0.12.1}/README.md +168 -11
- {stata_code-0.11.0 → stata_code-0.12.1}/SCHEMA.md +1 -1
- {stata_code-0.11.0 → stata_code-0.12.1}/docs/quickstart.zh.md +25 -1
- {stata_code-0.11.0 → stata_code-0.12.1}/pyproject.toml +8 -2
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/__init__.py +1 -1
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/cli.py +264 -3
- stata_code-0.12.1/stata_code/core/daemon.py +570 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/runner.py +5 -22
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/mcp/server.py +1 -1
- stata_code-0.12.1/tests/test_daemon.py +423 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_runner.py +12 -1
- {stata_code-0.11.0 → stata_code-0.12.1}/.gitignore +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/LICENSE +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/LICENSE-POLICY.md +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/PUBLISHING.md +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/docs/competitive-landscape.md +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/docs/design/hard_timeout.md +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/docs/industry-leader-roadmap.md +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/examples/01-basic-regression.md +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/examples/02-did-card-krueger.md +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/examples/03-graphs.md +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/examples/04-multi-session.md +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/examples/05-large-matrix.md +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/examples/06-cross-stack-parity-audit.md +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/examples/07-data-mcp-handoff.md +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/examples/README.md +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/schema/run_result.schema.json +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/scripts/build_skill_zip.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/scripts/build_standalone.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/scripts/check_github_actions.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/scripts/check_versions.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/scripts/export_schema.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/__main__.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/__init__.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/_pool.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/_refs.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/_runtime.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/console.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/errors.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/estimation.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/handoff.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/jobs.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/lint.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/log_artifacts.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/notebook.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/policy.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/provenance.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/run_index.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/core/schema.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/doctor.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/kernel/__init__.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/kernel/__main__.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/kernel/assets/logo-32x32.png +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/kernel/assets/logo-64x64.png +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/kernel/assets/logo-svg.svg +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/kernel/kernel.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/mcp/__init__.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/mcp/__main__.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/stata_code/mcp_setup.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/__init__.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/conftest.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/fixtures/.gitkeep +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_agent_ergonomics.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_benchmark.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_bugfix_regressions.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_cancel.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_cli.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_console.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_doctor.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_errors.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_estimation.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_github_actions.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_handoff.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_kernel.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_lint.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_lint_mcp.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_log_artifacts.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_mcp.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_mcp_kernel_extra.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_mcp_transport.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_method_prompts.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_new_tools.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_notebook.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_notebook_phase2.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_policy.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_pool.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_pool_refs_extra.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_provenance.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_public_api.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_real_stata.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_release_versions.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_run_index.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_runner_helpers.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_runtime_discovery.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_schema.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_schema_artifact.py +0 -0
- {stata_code-0.11.0 → stata_code-0.12.1}/tests/test_skill_package.py +0 -0
|
@@ -6,6 +6,103 @@ to semver-major.minor for the result schema (see `SCHEMA.md` §6).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## 0.12.1 — 2026-07-28
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **`pip install "stata-code[mcp]"` resolved a broken MCP server.** The extra
|
|
14
|
+
declared `mcp>=1.27` with no upper bound, and the upstream SDK has since
|
|
15
|
+
released 2.0 — a breaking rewrite in which `Server` no longer exposes the
|
|
16
|
+
`list_tools` / `call_tool` decorators this server registers through, and
|
|
17
|
+
`Tool` / `ToolAnnotations` renamed every camelCase field (`inputSchema` →
|
|
18
|
+
`input_schema`, `readOnlyHint` → `read_only_hint`, …). A fresh install
|
|
19
|
+
therefore pulled mcp 2.x and `stata-code-mcp` failed at import. The extra is
|
|
20
|
+
now `mcp>=1.27,<2`; the ceiling comes off together with the 2.x port.
|
|
21
|
+
|
|
22
|
+
Existing installs pinned to mcp 1.x were unaffected. **0.12.0 carries the
|
|
23
|
+
unbounded requirement and should be avoided** — install 0.12.1 or later.
|
|
24
|
+
|
|
25
|
+
## 0.12.0 — 2026-07-28
|
|
26
|
+
|
|
27
|
+
Adds a resident session daemon so the CLI can keep data in memory across
|
|
28
|
+
invocations, and fixes a VS Code regression introduced by 0.11.0's payload
|
|
29
|
+
defaults.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
#### Resident session daemon — state across CLI invocations
|
|
34
|
+
|
|
35
|
+
The subprocess pool already kept one Stata worker alive per session, but a plain
|
|
36
|
+
`stata-code run` owned that pool and tore it down on exit, so two consecutive CLI
|
|
37
|
+
calls could not share data in memory. The new `daemon` subcommand holds the pool
|
|
38
|
+
in a resident process behind a Unix socket:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
stata-code run --daemon -e 'sysuse auto, clear' -e 'gen z = price/1000'
|
|
42
|
+
stata-code run --daemon -e 'summarize z' # new process, z is still loaded
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- **`stata-code run --daemon`** routes the run through the daemon, starting it on
|
|
46
|
+
demand. `--socket` overrides the path. Graph refs are resolved back over the
|
|
47
|
+
socket, so `--graphs DIR` keeps working.
|
|
48
|
+
- **`stata-code daemon start | stop | restart | status`** for explicit control;
|
|
49
|
+
`status --json` is machine-readable.
|
|
50
|
+
- **Idle retirement** after 30 minutes by default (`--idle-timeout`, `0` disables)
|
|
51
|
+
so a forgotten daemon does not hold a Stata license slot.
|
|
52
|
+
- **Unix socket only, never TCP** — mode 0600 inside a mode-0700 directory. The
|
|
53
|
+
command-safety guard still applies to everything the daemon runs.
|
|
54
|
+
- **Requires the pystata backend.** `--backend console` is stateless batch
|
|
55
|
+
execution, so combining it with `--daemon` is a hard error rather than a silent
|
|
56
|
+
no-op.
|
|
57
|
+
- Falls back to a short hashed `/tmp` socket path when the natural location would
|
|
58
|
+
exceed the platform's ~104-byte `sun_path` limit; every entry point derives the
|
|
59
|
+
same fallback, so clients still find the daemon.
|
|
60
|
+
|
|
61
|
+
#### VS Code Outputs panel now lists what every run wrote
|
|
62
|
+
|
|
63
|
+
The panel read only `log.files.output_paths` — the archived copies inside a run
|
|
64
|
+
bundle — so it stayed empty unless the caller had opted into
|
|
65
|
+
`persist_log_files`. It now also reads `result.outputs`, the files a run wrote
|
|
66
|
+
in its working directory (reported on every run since 0.11.0), shown with their
|
|
67
|
+
size and listed *ahead* of bundle copies, since the working-directory file is
|
|
68
|
+
the one you want to open. Nodes carry an `origin` of `workdir` or `bundle`, and
|
|
69
|
+
an overwritten file says so in its tooltip.
|
|
70
|
+
|
|
71
|
+
### Fixed
|
|
72
|
+
|
|
73
|
+
- **VS Code: r()/e() matrices displayed as `0x0` and opened empty.** 0.11.0
|
|
74
|
+
made `include_results: "scalars"` the server default, which returns each
|
|
75
|
+
matrix as a `matrix://` stub with `rows` / `cols` elided (the true shape moved
|
|
76
|
+
to `n_rows` / `n_cols`). The extension's sidebar derives its dimension label
|
|
77
|
+
from `rows.length` / `cols.length` and renders `matrix.values` directly, so
|
|
78
|
+
every matrix showed `0x0` and exported an empty table. The extension now
|
|
79
|
+
requests `include_results: "full"` — an editor is a human surface with no
|
|
80
|
+
token budget to defend, matching what the Jupyter kernel already did. The
|
|
81
|
+
argument builder moved to a `vscode`-free `runArgs.ts` so the behaviour is
|
|
82
|
+
covered by unit tests.
|
|
83
|
+
- Documentation: the error taxonomy was described as "32 kinds" in `README.md`,
|
|
84
|
+
`README.en.md`, and `SCHEMA.md` while `ErrorKind` has had 34 members (the
|
|
85
|
+
English README's prose already said 34, contradicting its own table).
|
|
86
|
+
- Documentation: both READMEs listed "Stata 17+" as a hard install requirement,
|
|
87
|
+
which contradicted the console backend's documented Stata 13+ support.
|
|
88
|
+
- `README.md` was missing the command-line, client-setup, and command-safety
|
|
89
|
+
sections that `README.en.md` has had since 0.11.0.
|
|
90
|
+
- Documentation: `SKILL.md` and the agent reference library still described the
|
|
91
|
+
pre-0.11 surface — 18 tools, 31 error kinds, no mention of matrix stubs,
|
|
92
|
+
background runs, `result.outputs`, or `error.source_file`. Counts in every doc
|
|
93
|
+
are now checked against the code.
|
|
94
|
+
|
|
95
|
+
### Internal
|
|
96
|
+
|
|
97
|
+
- `_collect_returns` no longer takes a `request_id` or creates `matrix://` refs.
|
|
98
|
+
Nothing passed it — neither the production path nor any test — and ref
|
|
99
|
+
creation belongs to `_project_returns`, which owns the payload budget.
|
|
100
|
+
- `test_varname_not_found_emits_did_you_mean` failed once and could not be
|
|
101
|
+
reproduced. Its fuzzy match draws candidates from `dataset.variables`, so a
|
|
102
|
+
lost `auto.dta` in the shared in-process Stata degrades the suggestion for
|
|
103
|
+
reasons unrelated to the matching logic. The assertion was **split**, not
|
|
104
|
+
loosened: a precondition check now reports which of the two it was.
|
|
105
|
+
|
|
9
106
|
## 0.11.0 — 2026-07-28
|
|
10
107
|
|
|
11
108
|
An agent-ergonomics release, driven by a report from an agent that used the MCP
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stata-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.1
|
|
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
|
|
@@ -25,7 +25,7 @@ Requires-Python: >=3.10
|
|
|
25
25
|
Requires-Dist: pydantic>=2.0
|
|
26
26
|
Provides-Extra: all
|
|
27
27
|
Requires-Dist: ipykernel>=6.0; extra == 'all'
|
|
28
|
-
Requires-Dist: mcp
|
|
28
|
+
Requires-Dist: mcp<2,>=1.27; extra == 'all'
|
|
29
29
|
Provides-Extra: dev
|
|
30
30
|
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
31
31
|
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
@@ -34,7 +34,7 @@ Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
|
34
34
|
Provides-Extra: kernel
|
|
35
35
|
Requires-Dist: ipykernel>=6.0; extra == 'kernel'
|
|
36
36
|
Provides-Extra: mcp
|
|
37
|
-
Requires-Dist: mcp
|
|
37
|
+
Requires-Dist: mcp<2,>=1.27; extra == 'mcp'
|
|
38
38
|
Description-Content-Type: text/markdown
|
|
39
39
|
|
|
40
40
|
<p align="center">
|
|
@@ -120,7 +120,7 @@ Then just ask:
|
|
|
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
|
-
**Status: v0.
|
|
123
|
+
**Status: v0.12 (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. v0.12 adds a resident session daemon so consecutive CLI runs share data in memory, on top of v0.11's agent-ergonomics work: bounded result payloads, background execution for multi-minute jobs, error localization inside `do`-files, automatic log-handle cleanup, and generated-file reporting — see the [changelog](CHANGELOG.md). 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
125
|
Four workflows the current tree explicitly supports for end users and agents:
|
|
126
126
|
|
|
@@ -158,7 +158,9 @@ For the project's clean-room policy around AGPL/GPL Stata projects, see [LICENSE
|
|
|
158
158
|
|
|
159
159
|
## Install
|
|
160
160
|
|
|
161
|
-
Requirements: **Stata 17+** (
|
|
161
|
+
Requirements: **Python 3.10+**, plus either **Stata 17+** (using the `pystata`
|
|
162
|
+
shipped by Stata, for in-memory sessions) or **Stata 13+** (via the console
|
|
163
|
+
backend, which needs no `pystata` and is stateless).
|
|
162
164
|
|
|
163
165
|
```bash
|
|
164
166
|
# from PyPI
|
|
@@ -264,6 +266,54 @@ CI workflow template at
|
|
|
264
266
|
bundles the runtime and needs no Python install. Paired with `--backend console`,
|
|
265
267
|
it is a fully Python-free path to typed Stata results.
|
|
266
268
|
|
|
269
|
+
### The Session Daemon (State Across Invocations)
|
|
270
|
+
|
|
271
|
+
By default every `stata-code run` starts a fresh process, so in-memory data dies
|
|
272
|
+
with it. The `daemon` subcommand moves the subprocess pool into a resident
|
|
273
|
+
process behind a Unix socket, and consecutive `run --daemon` calls then land in
|
|
274
|
+
the **same Stata session**:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
stata-code run --daemon -e 'sysuse auto, clear' -e 'gen z = price/1000'
|
|
278
|
+
stata-code run --daemon -e 'summarize z' # new process, z is still there
|
|
279
|
+
stata-code run --daemon -e 'regress price mpg'
|
|
280
|
+
stata-code run --daemon -e 'display e(r2)' # e() from the previous process too
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The first `run --daemon` starts the daemon on demand — no manual step needed. You
|
|
284
|
+
can also drive it explicitly:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
stata-code daemon start # detached (--foreground to stay attached)
|
|
288
|
+
stata-code daemon status # pid, uptime, live sessions
|
|
289
|
+
stata-code daemon status --json
|
|
290
|
+
stata-code daemon stop
|
|
291
|
+
stata-code daemon restart
|
|
292
|
+
stata-code daemon start --idle-timeout 0 # 0 disables idle retirement
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Worth knowing:
|
|
296
|
+
|
|
297
|
+
- **Session isolation is unchanged.** `--session other` is still a separate Stata
|
|
298
|
+
frame / worker inside the daemon; sessions cannot see each other's data.
|
|
299
|
+
- **It retires itself after 30 idle minutes** so a forgotten daemon does not sit on
|
|
300
|
+
a Stata license slot. Tune with `--idle-timeout`; `0` disables it.
|
|
301
|
+
- **Unix socket only, never TCP.** The daemon executes arbitrary Stata code, so it
|
|
302
|
+
is deliberately reachable only through a mode-0600 socket inside a mode-0700
|
|
303
|
+
directory. The [command-safety](#command-safety) guard still applies.
|
|
304
|
+
- **Requires the pystata backend.** `--backend console` is stateless batch
|
|
305
|
+
execution by design, so pairing it with a daemon is an error rather than a
|
|
306
|
+
silent no-op.
|
|
307
|
+
- **Long socket paths degrade gracefully.** `sun_path` caps out around 104 bytes;
|
|
308
|
+
when the natural location is longer (a deep home directory, a long
|
|
309
|
+
`XDG_RUNTIME_DIR`), the socket moves to a short `/tmp` path derived from a hash
|
|
310
|
+
of the intended one. Every entry point derives it the same way, so clients still
|
|
311
|
+
find the daemon.
|
|
312
|
+
|
|
313
|
+
Plain `stata-code run`, `run --daemon`, and the MCP server all drive the same
|
|
314
|
+
engine and return the same `RunResult` schema — they differ only in how long the
|
|
315
|
+
Stata session lives.
|
|
316
|
+
|
|
267
317
|
### One-Command Client Setup
|
|
268
318
|
|
|
269
319
|
`stata-code setup` writes the MCP server entry into a client's config — the
|
|
@@ -513,11 +563,14 @@ The extension still requires the MCP extra on your system Python (`pip install "
|
|
|
513
563
|
|
|
514
564
|
## Token-Economy Defaults
|
|
515
565
|
|
|
516
|
-
A typical `stata_run` response is about **10x smaller** than servers that dump logs and images directly.
|
|
566
|
+
A typical `stata_run` response is about **10x smaller** than servers that dump logs and images directly. Four design choices drive this:
|
|
567
|
+
|
|
568
|
+
1. **Logs return `head` + `tail` + `ref`** by default. Full logs are fetched on demand via `get_log(ref)`, or grepped in place with `search_log(ref, pattern)`. A Stata regression log can be about 6,000 tokens; `stata-code` returns about 600 by default.
|
|
569
|
+
2. **Graphs return refs, not inline base64**. A 30 KB PNG can become about 50,000 base64 tokens; returning a ref avoids that unless the agent actually needs the bytes. When they are requested inline, they come back as real MCP image content blocks — viewable by a vision-capable client, rather than base64 buried in a JSON string that costs tokens and shows nothing.
|
|
570
|
+
3. **Each estimation is described once.** By default (`include_results: "scalars"`) `r()` / `e()` scalars and macros are inline, while every matrix becomes a `matrix://` stub carrying just its shape. Without this, one regression ships the same numbers four times — `e(b)`, `e(V)`'s label lists, `e(beta)`, `r(table)` — on top of `results.estimation`, which already holds the typed coefficient table. A 123-term regression drops from ~57 KB to ~28 KB. Raw values stay one `get_matrix(ref)` away, and `include_results: "full"` restores the old shape.
|
|
571
|
+
4. **Errors are typed**. Agents can check `err.kind == "varname_not_found"` instead of regex-parsing English logs.
|
|
517
572
|
|
|
518
|
-
|
|
519
|
-
2. **Graphs return refs, not inline base64**. A 30 KB PNG can become about 50,000 base64 tokens; returning a ref avoids that unless the agent actually needs the bytes.
|
|
520
|
-
3. **Errors are typed**. Agents can check `err.kind == "varname_not_found"` instead of regex-parsing English logs.
|
|
573
|
+
Two further knobs matter for fixed-effect-heavy work: `include_estimation: "summary"` keeps the model-level block and drops per-term rows, and `max_coefficients` caps the table. Either way `estimation.n_coefficients` reports the model's true size and `coefficients_truncated` flags the cut, so a trimmed table is never mistaken for a smaller model.
|
|
521
574
|
|
|
522
575
|
For example, a misspelled variable returns a structured error:
|
|
523
576
|
|
|
@@ -529,6 +582,7 @@ For example, a misspelled variable returns a structured error:
|
|
|
529
582
|
"kind": "varname_not_found",
|
|
530
583
|
"varname": "mpgg",
|
|
531
584
|
"line": 3,
|
|
585
|
+
"source_file": null,
|
|
532
586
|
"context": {
|
|
533
587
|
"before": ["use auto"],
|
|
534
588
|
"failing": "summarize mpgg",
|
|
@@ -536,11 +590,19 @@ For example, a misspelled variable returns a structured error:
|
|
|
536
590
|
},
|
|
537
591
|
"suggestions": [
|
|
538
592
|
{"action": "Did you mean `mpg`?", "command": "describe"}
|
|
539
|
-
]
|
|
593
|
+
],
|
|
594
|
+
"recovery": {
|
|
595
|
+
"category": "user_code",
|
|
596
|
+
"retriable": false,
|
|
597
|
+
"needs_code_change": true,
|
|
598
|
+
"needs_user_input": false
|
|
599
|
+
}
|
|
540
600
|
}
|
|
541
601
|
}
|
|
542
602
|
```
|
|
543
603
|
|
|
604
|
+
When the failing command lives inside a script you invoked (`do "analysis.do"`), `line` and `context` point *inside that script* and `source_file` names it — so an agent does not have to re-read the file to find the offending line.
|
|
605
|
+
|
|
544
606
|
The full schema is in [SCHEMA.md](SCHEMA.md).
|
|
545
607
|
|
|
546
608
|
---
|
|
@@ -576,7 +638,7 @@ stata_code/
|
|
|
576
638
|
| Standalone MCP | ✓ | ✓ | bundled with VS Code | — (bespoke HTTP + copy-paste) | — |
|
|
577
639
|
| Jupyter kernel | ✓ | — | — | — | ✓ |
|
|
578
640
|
| Unified result schema | ✓ ([SCHEMA.md](SCHEMA.md)) | per-tool | per-tool | raw log to the agent | per-tool |
|
|
579
|
-
| Typed errors + suggestions | ✓ (
|
|
641
|
+
| Typed errors + suggestions | ✓ (34 kinds) | — | — | — | — |
|
|
580
642
|
| Token-economy defaults | ✓ (log refs, graph refs) | — | — | — | — |
|
|
581
643
|
| Command-safety guard | ✓ (`shell`/`erase`/`rmdir`/`!`) | ✓ (27-rule) | — | — | — |
|
|
582
644
|
| Bash / plain-terminal CLI | ✓ (`stata-code run`) | ✓ | — | — | — |
|
|
@@ -590,7 +652,7 @@ stata_code/
|
|
|
590
652
|
|
|
591
653
|
`stata-code` owns the **agent-native, typed-contract** lane: one structured
|
|
592
654
|
`RunResult` schema across MCP, Jupyter, VS Code, and a plain-terminal CLI; a
|
|
593
|
-
|
|
655
|
+
34-kind error taxonomy with recovery contracts; token-economy refs; and now a
|
|
594
656
|
console backend (Stata 13+) plus a zero-Python binary. Editor-first tools like
|
|
595
657
|
`stata-all-in-one` lead on human IDE polish and hand the agent raw log text;
|
|
596
658
|
`stata-code` matches their onboarding/version reach while keeping the typed
|
|
@@ -598,6 +660,49 @@ execution contract they don't have. See
|
|
|
598
660
|
[docs/competitive-landscape.md](docs/competitive-landscape.md) for the full
|
|
599
661
|
teardown.
|
|
600
662
|
|
|
663
|
+
### Versus calling the Stata CLI directly
|
|
664
|
+
|
|
665
|
+
To be clear up front: **you can drive Stata from a shell with no tooling at all.**
|
|
666
|
+
Most Stata installs ship a command-line executable, and for one-shot execution it
|
|
667
|
+
is perfectly good — no reason to add a dependency:
|
|
668
|
+
|
|
669
|
+
```bash
|
|
670
|
+
/Applications/Stata/StataMP.app/Contents/MacOS/stata-mp -b do analysis.do
|
|
671
|
+
printf 'sysuse auto, clear\nsummarize mpg\nexit, clear\n' | stata-mp -q
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
What's worth knowing is where that path breaks down. Both columns below drive the
|
|
675
|
+
**same Stata binary and parse the same log** (`stata-code` via `--backend console`);
|
|
676
|
+
the only difference is the wrapper around it:
|
|
677
|
+
|
|
678
|
+
| | plain `stata-mp -b` | `stata-code run` |
|
|
679
|
+
| --- | --- | --- |
|
|
680
|
+
| Exit code on error | **`0`** — fails silently | `1` |
|
|
681
|
+
| How you learn it failed | grep the log for `r(111);` yourself | `ok=False`, `rc=111` |
|
|
682
|
+
| Error classification | none | `[varname_not_found]` (34 kinds) |
|
|
683
|
+
| Recovery hint | none | "Run `describe` to list available variables" |
|
|
684
|
+
| Getting a coefficient | scrape the aligned ASCII table | `e.scalars["r2"] → 0.6515312529087511` (float) |
|
|
685
|
+
| State across calls | no | `run --daemon` (see below) |
|
|
686
|
+
|
|
687
|
+
The first row is the one that bites: Stata's batch mode **returns exit code 0 even
|
|
688
|
+
when the code errors out**. In an automated loop, an agent sees `exit 0`, moves on,
|
|
689
|
+
and treats a failed run's output as valid.
|
|
690
|
+
|
|
691
|
+
Rule of thumb:
|
|
692
|
+
|
|
693
|
+
- **One-shot execution** ("run this do-file and show me the output") — the plain
|
|
694
|
+
CLI is fine; don't over-engineer it.
|
|
695
|
+
- **Scripts / CI** (anything that branches on success) — use `stata-code run` and
|
|
696
|
+
get a trustworthy signal for near-zero cost.
|
|
697
|
+
- **Iterative analysis** (many rounds, reading intermediate results, deciding the
|
|
698
|
+
next step from `e()`) — use `run --daemon` or MCP; otherwise statelessness and
|
|
699
|
+
text-scraping compound round over round.
|
|
700
|
+
|
|
701
|
+
> Without `--daemon`, `stata-code run` **starts a fresh process per invocation**:
|
|
702
|
+
> multiple `-e` snippets in one call share a session, but in-memory data is gone
|
|
703
|
+
> once the process exits. With `--daemon` the data stays in a resident process —
|
|
704
|
+
> see [the session daemon](#the-session-daemon-state-across-invocations).
|
|
705
|
+
|
|
601
706
|
---
|
|
602
707
|
|
|
603
708
|
## Roadmap
|
|
@@ -607,11 +712,12 @@ teardown.
|
|
|
607
712
|
- v1.0 result schema ([SCHEMA.md](SCHEMA.md))
|
|
608
713
|
- `pystata`-based runner with native-typed `r()`, `e()`, and matrices
|
|
609
714
|
- Multi-session via Stata frames (`session_id` accepts `[A-Za-z0-9_-]+`; ids such as `model-a` are mapped to private legal frame names internally while the public id is echoed back)
|
|
715
|
+
- Resident session daemon (`stata-code daemon`, `run --daemon`) so CLI invocations share one live Stata session over a mode-0600 Unix socket, with idle retirement
|
|
610
716
|
- Per-line error attribution: line number, context, commands_executed
|
|
611
717
|
- Graph capture: `png` / `svg` / `pdf` with ref store and source-command attribution
|
|
612
718
|
- Log truncation with ref store
|
|
613
719
|
- Warning extraction: 5 categories + generic notes
|
|
614
|
-
-
|
|
720
|
+
- 34-kind error taxonomy with canonical suggestions and a machine-readable `recovery` verdict (retriable / needs-code-change / needs-user-input)
|
|
615
721
|
- 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
722
|
- 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
723
|
- 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
|
|
@@ -619,8 +725,12 @@ teardown.
|
|
|
619
725
|
- 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
726
|
- One-click VS Code onboarding: the extension offers to create a workspace `.venv` and install the server (command palette: “Stata: Set Up MCP Server”)
|
|
621
727
|
- Jupyter kernel: rewired to the v1.0 pipeline, kernel logos bundled
|
|
622
|
-
-
|
|
623
|
-
- Subprocess-backed hard timeout and cancellation for the public Python API and MCP server: `timeout_ms
|
|
728
|
+
- Result-payload budget: `include_results` (matrices become `matrix://` stubs by default), `include_estimation`, and `max_coefficients`, with `get_matrix(ref)` to pull raw values on demand
|
|
729
|
+
- Subprocess-backed hard timeout and cancellation for the public Python API and MCP server: `timeout_ms` (now an advertised `stata_run` argument, and it budgets queueing too — contention returns `rc=-5` / `session_busy` instead of blocking), `cancel(session_id)`, and MCP `cancel_session`
|
|
730
|
+
- Background execution for multi-minute jobs: `run_in_background` returns a job id; `stata_run_status` (with a bounded `wait_ms`) and `list_background_runs` poll it
|
|
731
|
+
- Error localization inside `do`/`run` scripts: `error.line` + `error.context` resolve within the invoked file and `error.source_file` names it; a failed run still carries a full searchable log
|
|
732
|
+
- Log-handle hygiene: handles leaked by a failed run are closed automatically (`auto_close_logs`), so an aborted script cannot make every later run in that session fail with r(604)
|
|
733
|
+
- Generated-file reporting: `result.outputs` lists the tables, exports and datasets each run wrote, independent of the run-bundle options
|
|
624
734
|
- Per-cell repair loop on `.ipynb` via `notebook_outline` / `notebook_get_cell` / `notebook_edit_cell` with optimistic-concurrency `expected_source` guards and `origin_cell_id` echo on `RunResult`
|
|
625
735
|
- Persistent run bundles + `list_runs` query over `manifest.json` files (filter by cell / origin / session / since / ok; page with limit / offset)
|
|
626
736
|
- Read-only `stata-code doctor` / `verify` diagnostics for package version,
|
|
@@ -635,7 +745,7 @@ teardown.
|
|
|
635
745
|
|
|
636
746
|
### Next Up
|
|
637
747
|
|
|
638
|
-
- Streaming / progress for long runs (`log.complete:false`,
|
|
748
|
+
- Streaming / incremental progress for long runs (`log.complete:false`, partial log lines). v0.11's `run_in_background` already unblocks the caller for 20-minute `boottest` / `csdid` jobs, but a running job still reports nothing until it finishes
|
|
639
749
|
- Hard timeout / cancellation for the Jupyter kernel (move it from the direct in-process runner to the subprocess pool, or an equivalent)
|
|
640
750
|
- Console backend: graph capture and richer matrix coverage (values currently materialized for the estimation matrices; state is per-call)
|
|
641
751
|
- 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
|
|
@@ -81,7 +81,7 @@ Then just ask:
|
|
|
81
81
|
|
|
82
82
|
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.
|
|
83
83
|
|
|
84
|
-
**Status: v0.
|
|
84
|
+
**Status: v0.12 (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. v0.12 adds a resident session daemon so consecutive CLI runs share data in memory, on top of v0.11's agent-ergonomics work: bounded result payloads, background execution for multi-minute jobs, error localization inside `do`-files, automatic log-handle cleanup, and generated-file reporting — see the [changelog](CHANGELOG.md). 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**.
|
|
85
85
|
|
|
86
86
|
Four workflows the current tree explicitly supports for end users and agents:
|
|
87
87
|
|
|
@@ -119,7 +119,9 @@ For the project's clean-room policy around AGPL/GPL Stata projects, see [LICENSE
|
|
|
119
119
|
|
|
120
120
|
## Install
|
|
121
121
|
|
|
122
|
-
Requirements: **Stata 17+** (
|
|
122
|
+
Requirements: **Python 3.10+**, plus either **Stata 17+** (using the `pystata`
|
|
123
|
+
shipped by Stata, for in-memory sessions) or **Stata 13+** (via the console
|
|
124
|
+
backend, which needs no `pystata` and is stateless).
|
|
123
125
|
|
|
124
126
|
```bash
|
|
125
127
|
# from PyPI
|
|
@@ -225,6 +227,54 @@ CI workflow template at
|
|
|
225
227
|
bundles the runtime and needs no Python install. Paired with `--backend console`,
|
|
226
228
|
it is a fully Python-free path to typed Stata results.
|
|
227
229
|
|
|
230
|
+
### The Session Daemon (State Across Invocations)
|
|
231
|
+
|
|
232
|
+
By default every `stata-code run` starts a fresh process, so in-memory data dies
|
|
233
|
+
with it. The `daemon` subcommand moves the subprocess pool into a resident
|
|
234
|
+
process behind a Unix socket, and consecutive `run --daemon` calls then land in
|
|
235
|
+
the **same Stata session**:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
stata-code run --daemon -e 'sysuse auto, clear' -e 'gen z = price/1000'
|
|
239
|
+
stata-code run --daemon -e 'summarize z' # new process, z is still there
|
|
240
|
+
stata-code run --daemon -e 'regress price mpg'
|
|
241
|
+
stata-code run --daemon -e 'display e(r2)' # e() from the previous process too
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The first `run --daemon` starts the daemon on demand — no manual step needed. You
|
|
245
|
+
can also drive it explicitly:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
stata-code daemon start # detached (--foreground to stay attached)
|
|
249
|
+
stata-code daemon status # pid, uptime, live sessions
|
|
250
|
+
stata-code daemon status --json
|
|
251
|
+
stata-code daemon stop
|
|
252
|
+
stata-code daemon restart
|
|
253
|
+
stata-code daemon start --idle-timeout 0 # 0 disables idle retirement
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Worth knowing:
|
|
257
|
+
|
|
258
|
+
- **Session isolation is unchanged.** `--session other` is still a separate Stata
|
|
259
|
+
frame / worker inside the daemon; sessions cannot see each other's data.
|
|
260
|
+
- **It retires itself after 30 idle minutes** so a forgotten daemon does not sit on
|
|
261
|
+
a Stata license slot. Tune with `--idle-timeout`; `0` disables it.
|
|
262
|
+
- **Unix socket only, never TCP.** The daemon executes arbitrary Stata code, so it
|
|
263
|
+
is deliberately reachable only through a mode-0600 socket inside a mode-0700
|
|
264
|
+
directory. The [command-safety](#command-safety) guard still applies.
|
|
265
|
+
- **Requires the pystata backend.** `--backend console` is stateless batch
|
|
266
|
+
execution by design, so pairing it with a daemon is an error rather than a
|
|
267
|
+
silent no-op.
|
|
268
|
+
- **Long socket paths degrade gracefully.** `sun_path` caps out around 104 bytes;
|
|
269
|
+
when the natural location is longer (a deep home directory, a long
|
|
270
|
+
`XDG_RUNTIME_DIR`), the socket moves to a short `/tmp` path derived from a hash
|
|
271
|
+
of the intended one. Every entry point derives it the same way, so clients still
|
|
272
|
+
find the daemon.
|
|
273
|
+
|
|
274
|
+
Plain `stata-code run`, `run --daemon`, and the MCP server all drive the same
|
|
275
|
+
engine and return the same `RunResult` schema — they differ only in how long the
|
|
276
|
+
Stata session lives.
|
|
277
|
+
|
|
228
278
|
### One-Command Client Setup
|
|
229
279
|
|
|
230
280
|
`stata-code setup` writes the MCP server entry into a client's config — the
|
|
@@ -474,11 +524,14 @@ The extension still requires the MCP extra on your system Python (`pip install "
|
|
|
474
524
|
|
|
475
525
|
## Token-Economy Defaults
|
|
476
526
|
|
|
477
|
-
A typical `stata_run` response is about **10x smaller** than servers that dump logs and images directly.
|
|
527
|
+
A typical `stata_run` response is about **10x smaller** than servers that dump logs and images directly. Four design choices drive this:
|
|
528
|
+
|
|
529
|
+
1. **Logs return `head` + `tail` + `ref`** by default. Full logs are fetched on demand via `get_log(ref)`, or grepped in place with `search_log(ref, pattern)`. A Stata regression log can be about 6,000 tokens; `stata-code` returns about 600 by default.
|
|
530
|
+
2. **Graphs return refs, not inline base64**. A 30 KB PNG can become about 50,000 base64 tokens; returning a ref avoids that unless the agent actually needs the bytes. When they are requested inline, they come back as real MCP image content blocks — viewable by a vision-capable client, rather than base64 buried in a JSON string that costs tokens and shows nothing.
|
|
531
|
+
3. **Each estimation is described once.** By default (`include_results: "scalars"`) `r()` / `e()` scalars and macros are inline, while every matrix becomes a `matrix://` stub carrying just its shape. Without this, one regression ships the same numbers four times — `e(b)`, `e(V)`'s label lists, `e(beta)`, `r(table)` — on top of `results.estimation`, which already holds the typed coefficient table. A 123-term regression drops from ~57 KB to ~28 KB. Raw values stay one `get_matrix(ref)` away, and `include_results: "full"` restores the old shape.
|
|
532
|
+
4. **Errors are typed**. Agents can check `err.kind == "varname_not_found"` instead of regex-parsing English logs.
|
|
478
533
|
|
|
479
|
-
|
|
480
|
-
2. **Graphs return refs, not inline base64**. A 30 KB PNG can become about 50,000 base64 tokens; returning a ref avoids that unless the agent actually needs the bytes.
|
|
481
|
-
3. **Errors are typed**. Agents can check `err.kind == "varname_not_found"` instead of regex-parsing English logs.
|
|
534
|
+
Two further knobs matter for fixed-effect-heavy work: `include_estimation: "summary"` keeps the model-level block and drops per-term rows, and `max_coefficients` caps the table. Either way `estimation.n_coefficients` reports the model's true size and `coefficients_truncated` flags the cut, so a trimmed table is never mistaken for a smaller model.
|
|
482
535
|
|
|
483
536
|
For example, a misspelled variable returns a structured error:
|
|
484
537
|
|
|
@@ -490,6 +543,7 @@ For example, a misspelled variable returns a structured error:
|
|
|
490
543
|
"kind": "varname_not_found",
|
|
491
544
|
"varname": "mpgg",
|
|
492
545
|
"line": 3,
|
|
546
|
+
"source_file": null,
|
|
493
547
|
"context": {
|
|
494
548
|
"before": ["use auto"],
|
|
495
549
|
"failing": "summarize mpgg",
|
|
@@ -497,11 +551,19 @@ For example, a misspelled variable returns a structured error:
|
|
|
497
551
|
},
|
|
498
552
|
"suggestions": [
|
|
499
553
|
{"action": "Did you mean `mpg`?", "command": "describe"}
|
|
500
|
-
]
|
|
554
|
+
],
|
|
555
|
+
"recovery": {
|
|
556
|
+
"category": "user_code",
|
|
557
|
+
"retriable": false,
|
|
558
|
+
"needs_code_change": true,
|
|
559
|
+
"needs_user_input": false
|
|
560
|
+
}
|
|
501
561
|
}
|
|
502
562
|
}
|
|
503
563
|
```
|
|
504
564
|
|
|
565
|
+
When the failing command lives inside a script you invoked (`do "analysis.do"`), `line` and `context` point *inside that script* and `source_file` names it — so an agent does not have to re-read the file to find the offending line.
|
|
566
|
+
|
|
505
567
|
The full schema is in [SCHEMA.md](SCHEMA.md).
|
|
506
568
|
|
|
507
569
|
---
|
|
@@ -537,7 +599,7 @@ stata_code/
|
|
|
537
599
|
| Standalone MCP | ✓ | ✓ | bundled with VS Code | — (bespoke HTTP + copy-paste) | — |
|
|
538
600
|
| Jupyter kernel | ✓ | — | — | — | ✓ |
|
|
539
601
|
| Unified result schema | ✓ ([SCHEMA.md](SCHEMA.md)) | per-tool | per-tool | raw log to the agent | per-tool |
|
|
540
|
-
| Typed errors + suggestions | ✓ (
|
|
602
|
+
| Typed errors + suggestions | ✓ (34 kinds) | — | — | — | — |
|
|
541
603
|
| Token-economy defaults | ✓ (log refs, graph refs) | — | — | — | — |
|
|
542
604
|
| Command-safety guard | ✓ (`shell`/`erase`/`rmdir`/`!`) | ✓ (27-rule) | — | — | — |
|
|
543
605
|
| Bash / plain-terminal CLI | ✓ (`stata-code run`) | ✓ | — | — | — |
|
|
@@ -551,7 +613,7 @@ stata_code/
|
|
|
551
613
|
|
|
552
614
|
`stata-code` owns the **agent-native, typed-contract** lane: one structured
|
|
553
615
|
`RunResult` schema across MCP, Jupyter, VS Code, and a plain-terminal CLI; a
|
|
554
|
-
|
|
616
|
+
34-kind error taxonomy with recovery contracts; token-economy refs; and now a
|
|
555
617
|
console backend (Stata 13+) plus a zero-Python binary. Editor-first tools like
|
|
556
618
|
`stata-all-in-one` lead on human IDE polish and hand the agent raw log text;
|
|
557
619
|
`stata-code` matches their onboarding/version reach while keeping the typed
|
|
@@ -559,6 +621,49 @@ execution contract they don't have. See
|
|
|
559
621
|
[docs/competitive-landscape.md](docs/competitive-landscape.md) for the full
|
|
560
622
|
teardown.
|
|
561
623
|
|
|
624
|
+
### Versus calling the Stata CLI directly
|
|
625
|
+
|
|
626
|
+
To be clear up front: **you can drive Stata from a shell with no tooling at all.**
|
|
627
|
+
Most Stata installs ship a command-line executable, and for one-shot execution it
|
|
628
|
+
is perfectly good — no reason to add a dependency:
|
|
629
|
+
|
|
630
|
+
```bash
|
|
631
|
+
/Applications/Stata/StataMP.app/Contents/MacOS/stata-mp -b do analysis.do
|
|
632
|
+
printf 'sysuse auto, clear\nsummarize mpg\nexit, clear\n' | stata-mp -q
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
What's worth knowing is where that path breaks down. Both columns below drive the
|
|
636
|
+
**same Stata binary and parse the same log** (`stata-code` via `--backend console`);
|
|
637
|
+
the only difference is the wrapper around it:
|
|
638
|
+
|
|
639
|
+
| | plain `stata-mp -b` | `stata-code run` |
|
|
640
|
+
| --- | --- | --- |
|
|
641
|
+
| Exit code on error | **`0`** — fails silently | `1` |
|
|
642
|
+
| How you learn it failed | grep the log for `r(111);` yourself | `ok=False`, `rc=111` |
|
|
643
|
+
| Error classification | none | `[varname_not_found]` (34 kinds) |
|
|
644
|
+
| Recovery hint | none | "Run `describe` to list available variables" |
|
|
645
|
+
| Getting a coefficient | scrape the aligned ASCII table | `e.scalars["r2"] → 0.6515312529087511` (float) |
|
|
646
|
+
| State across calls | no | `run --daemon` (see below) |
|
|
647
|
+
|
|
648
|
+
The first row is the one that bites: Stata's batch mode **returns exit code 0 even
|
|
649
|
+
when the code errors out**. In an automated loop, an agent sees `exit 0`, moves on,
|
|
650
|
+
and treats a failed run's output as valid.
|
|
651
|
+
|
|
652
|
+
Rule of thumb:
|
|
653
|
+
|
|
654
|
+
- **One-shot execution** ("run this do-file and show me the output") — the plain
|
|
655
|
+
CLI is fine; don't over-engineer it.
|
|
656
|
+
- **Scripts / CI** (anything that branches on success) — use `stata-code run` and
|
|
657
|
+
get a trustworthy signal for near-zero cost.
|
|
658
|
+
- **Iterative analysis** (many rounds, reading intermediate results, deciding the
|
|
659
|
+
next step from `e()`) — use `run --daemon` or MCP; otherwise statelessness and
|
|
660
|
+
text-scraping compound round over round.
|
|
661
|
+
|
|
662
|
+
> Without `--daemon`, `stata-code run` **starts a fresh process per invocation**:
|
|
663
|
+
> multiple `-e` snippets in one call share a session, but in-memory data is gone
|
|
664
|
+
> once the process exits. With `--daemon` the data stays in a resident process —
|
|
665
|
+
> see [the session daemon](#the-session-daemon-state-across-invocations).
|
|
666
|
+
|
|
562
667
|
---
|
|
563
668
|
|
|
564
669
|
## Roadmap
|
|
@@ -568,11 +673,12 @@ teardown.
|
|
|
568
673
|
- v1.0 result schema ([SCHEMA.md](SCHEMA.md))
|
|
569
674
|
- `pystata`-based runner with native-typed `r()`, `e()`, and matrices
|
|
570
675
|
- Multi-session via Stata frames (`session_id` accepts `[A-Za-z0-9_-]+`; ids such as `model-a` are mapped to private legal frame names internally while the public id is echoed back)
|
|
676
|
+
- Resident session daemon (`stata-code daemon`, `run --daemon`) so CLI invocations share one live Stata session over a mode-0600 Unix socket, with idle retirement
|
|
571
677
|
- Per-line error attribution: line number, context, commands_executed
|
|
572
678
|
- Graph capture: `png` / `svg` / `pdf` with ref store and source-command attribution
|
|
573
679
|
- Log truncation with ref store
|
|
574
680
|
- Warning extraction: 5 categories + generic notes
|
|
575
|
-
-
|
|
681
|
+
- 34-kind error taxonomy with canonical suggestions and a machine-readable `recovery` verdict (retriable / needs-code-change / needs-user-input)
|
|
576
682
|
- 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`)
|
|
577
683
|
- 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`
|
|
578
684
|
- 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
|
|
@@ -580,8 +686,12 @@ teardown.
|
|
|
580
686
|
- 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
|
|
581
687
|
- One-click VS Code onboarding: the extension offers to create a workspace `.venv` and install the server (command palette: “Stata: Set Up MCP Server”)
|
|
582
688
|
- Jupyter kernel: rewired to the v1.0 pipeline, kernel logos bundled
|
|
583
|
-
-
|
|
584
|
-
- Subprocess-backed hard timeout and cancellation for the public Python API and MCP server: `timeout_ms
|
|
689
|
+
- Result-payload budget: `include_results` (matrices become `matrix://` stubs by default), `include_estimation`, and `max_coefficients`, with `get_matrix(ref)` to pull raw values on demand
|
|
690
|
+
- Subprocess-backed hard timeout and cancellation for the public Python API and MCP server: `timeout_ms` (now an advertised `stata_run` argument, and it budgets queueing too — contention returns `rc=-5` / `session_busy` instead of blocking), `cancel(session_id)`, and MCP `cancel_session`
|
|
691
|
+
- Background execution for multi-minute jobs: `run_in_background` returns a job id; `stata_run_status` (with a bounded `wait_ms`) and `list_background_runs` poll it
|
|
692
|
+
- Error localization inside `do`/`run` scripts: `error.line` + `error.context` resolve within the invoked file and `error.source_file` names it; a failed run still carries a full searchable log
|
|
693
|
+
- Log-handle hygiene: handles leaked by a failed run are closed automatically (`auto_close_logs`), so an aborted script cannot make every later run in that session fail with r(604)
|
|
694
|
+
- Generated-file reporting: `result.outputs` lists the tables, exports and datasets each run wrote, independent of the run-bundle options
|
|
585
695
|
- Per-cell repair loop on `.ipynb` via `notebook_outline` / `notebook_get_cell` / `notebook_edit_cell` with optimistic-concurrency `expected_source` guards and `origin_cell_id` echo on `RunResult`
|
|
586
696
|
- Persistent run bundles + `list_runs` query over `manifest.json` files (filter by cell / origin / session / since / ok; page with limit / offset)
|
|
587
697
|
- Read-only `stata-code doctor` / `verify` diagnostics for package version,
|
|
@@ -596,7 +706,7 @@ teardown.
|
|
|
596
706
|
|
|
597
707
|
### Next Up
|
|
598
708
|
|
|
599
|
-
- Streaming / progress for long runs (`log.complete:false`,
|
|
709
|
+
- Streaming / incremental progress for long runs (`log.complete:false`, partial log lines). v0.11's `run_in_background` already unblocks the caller for 20-minute `boottest` / `csdid` jobs, but a running job still reports nothing until it finishes
|
|
600
710
|
- Hard timeout / cancellation for the Jupyter kernel (move it from the direct in-process runner to the subprocess pool, or an equivalent)
|
|
601
711
|
- Console backend: graph capture and richer matrix coverage (values currently materialized for the estimation matrices; state is per-call)
|
|
602
712
|
- 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
|