stata-code 0.8.1__tar.gz → 0.9.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.
Files changed (85) hide show
  1. {stata_code-0.8.1 → stata_code-0.9.1}/.gitignore +2 -0
  2. {stata_code-0.8.1 → stata_code-0.9.1}/CHANGELOG.md +217 -7
  3. {stata_code-0.8.1 → stata_code-0.9.1}/PKG-INFO +17 -6
  4. {stata_code-0.8.1 → stata_code-0.9.1}/PUBLISHING.md +16 -7
  5. {stata_code-0.8.1 → stata_code-0.9.1}/README.md +16 -5
  6. {stata_code-0.8.1 → stata_code-0.9.1}/SCHEMA.md +104 -21
  7. stata_code-0.9.1/docs/competitive-landscape.md +163 -0
  8. {stata_code-0.8.1 → stata_code-0.9.1}/docs/industry-leader-roadmap.md +15 -0
  9. {stata_code-0.8.1 → stata_code-0.9.1}/pyproject.toml +1 -1
  10. {stata_code-0.8.1 → stata_code-0.9.1}/schema/run_result.schema.json +284 -0
  11. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/__init__.py +41 -2
  12. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/cli.py +12 -0
  13. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/core/__init__.py +40 -1
  14. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/core/_pool.py +345 -103
  15. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/core/_runtime.py +13 -2
  16. stata_code-0.9.1/stata_code/core/errors.py +802 -0
  17. stata_code-0.9.1/stata_code/core/estimation.py +377 -0
  18. stata_code-0.9.1/stata_code/core/handoff.py +96 -0
  19. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/core/log_artifacts.py +3 -1
  20. stata_code-0.9.1/stata_code/core/provenance.py +250 -0
  21. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/core/runner.py +88 -55
  22. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/core/schema.py +96 -14
  23. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/doctor.py +170 -13
  24. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/kernel/kernel.py +6 -3
  25. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/mcp/server.py +5 -2
  26. stata_code-0.9.1/tests/test_bugfix_regressions.py +556 -0
  27. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_doctor.py +99 -4
  28. stata_code-0.9.1/tests/test_errors.py +462 -0
  29. stata_code-0.9.1/tests/test_estimation.py +273 -0
  30. stata_code-0.9.1/tests/test_handoff.py +85 -0
  31. stata_code-0.9.1/tests/test_mcp_kernel_extra.py +1087 -0
  32. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_pool.py +22 -1
  33. stata_code-0.9.1/tests/test_pool_refs_extra.py +993 -0
  34. stata_code-0.9.1/tests/test_provenance.py +193 -0
  35. stata_code-0.9.1/tests/test_real_stata.py +221 -0
  36. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_runner.py +16 -0
  37. stata_code-0.9.1/tests/test_runner_helpers.py +1121 -0
  38. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_schema.py +39 -2
  39. stata_code-0.8.1/stata_code/core/errors.py +0 -456
  40. stata_code-0.8.1/tests/test_errors.py +0 -231
  41. {stata_code-0.8.1 → stata_code-0.9.1}/LICENSE +0 -0
  42. {stata_code-0.8.1 → stata_code-0.9.1}/LICENSE-POLICY.md +0 -0
  43. {stata_code-0.8.1 → stata_code-0.9.1}/docs/design/hard_timeout.md +0 -0
  44. {stata_code-0.8.1 → stata_code-0.9.1}/examples/01-basic-regression.md +0 -0
  45. {stata_code-0.8.1 → stata_code-0.9.1}/examples/02-did-card-krueger.md +0 -0
  46. {stata_code-0.8.1 → stata_code-0.9.1}/examples/03-graphs.md +0 -0
  47. {stata_code-0.8.1 → stata_code-0.9.1}/examples/04-multi-session.md +0 -0
  48. {stata_code-0.8.1 → stata_code-0.9.1}/examples/05-large-matrix.md +0 -0
  49. {stata_code-0.8.1 → stata_code-0.9.1}/examples/06-cross-stack-parity-audit.md +0 -0
  50. {stata_code-0.8.1 → stata_code-0.9.1}/examples/07-data-mcp-handoff.md +0 -0
  51. {stata_code-0.8.1 → stata_code-0.9.1}/examples/README.md +0 -0
  52. {stata_code-0.8.1 → stata_code-0.9.1}/scripts/build_skill_zip.py +0 -0
  53. {stata_code-0.8.1 → stata_code-0.9.1}/scripts/check_github_actions.py +0 -0
  54. {stata_code-0.8.1 → stata_code-0.9.1}/scripts/check_versions.py +0 -0
  55. {stata_code-0.8.1 → stata_code-0.9.1}/scripts/export_schema.py +0 -0
  56. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/__main__.py +0 -0
  57. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/core/_refs.py +0 -0
  58. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/core/notebook.py +0 -0
  59. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/core/run_index.py +0 -0
  60. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/kernel/__init__.py +0 -0
  61. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/kernel/__main__.py +0 -0
  62. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/kernel/assets/logo-32x32.png +0 -0
  63. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/kernel/assets/logo-64x64.png +0 -0
  64. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/kernel/assets/logo-svg.svg +0 -0
  65. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/mcp/__init__.py +0 -0
  66. {stata_code-0.8.1 → stata_code-0.9.1}/stata_code/mcp/__main__.py +0 -0
  67. {stata_code-0.8.1 → stata_code-0.9.1}/tests/__init__.py +0 -0
  68. {stata_code-0.8.1 → stata_code-0.9.1}/tests/conftest.py +0 -0
  69. {stata_code-0.8.1 → stata_code-0.9.1}/tests/fixtures/.gitkeep +0 -0
  70. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_cancel.py +0 -0
  71. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_github_actions.py +0 -0
  72. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_kernel.py +0 -0
  73. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_log_artifacts.py +0 -0
  74. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_mcp.py +0 -0
  75. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_mcp_stdio.py +0 -0
  76. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_method_prompts.py +0 -0
  77. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_new_tools.py +0 -0
  78. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_notebook.py +0 -0
  79. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_notebook_phase2.py +0 -0
  80. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_public_api.py +0 -0
  81. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_release_versions.py +0 -0
  82. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_run_index.py +0 -0
  83. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_runtime_discovery.py +0 -0
  84. {stata_code-0.8.1 → stata_code-0.9.1}/tests/test_schema_artifact.py +0 -0
  85. {stata_code-0.8.1 → stata_code-0.9.1}/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,218 @@ 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.9.1 — 2026-07-22
10
+
11
+ Quality-hardening pass: correctness fixes from an adversarial code review,
12
+ a large offline test expansion, and a documentation accuracy audit. No new
13
+ features, no API or schema changes.
14
+
15
+ A second review round found that three of the first round's fixes were
16
+ incomplete or had introduced a regression of their own; those are the first
17
+ four entries below. The common failure was testing a fix in isolation while
18
+ the real call path stayed uncovered — the new tests go through `SessionPool`
19
+ rather than `WorkerProcess` wherever the pool is what carries the bug, and
20
+ each was confirmed to fail against the pre-fix source.
21
+
22
+ ### Fixed
23
+
24
+ - **A read-only status query no longer kills a healthy mid-run worker.**
25
+ Once `send_simple_op` learned to bail out rather than block on the worker
26
+ lock, that bail-out surfaced as a `_WorkerTimeout` — and
27
+ `SessionPool.stata_info` treated "busy" and "dead" identically: it
28
+ SIGTERMed the worker and dropped it from the pool. Calling the
29
+ `stata_info` tool during a long `bootstrap`/`reghdfe` therefore returned
30
+ the user's run as `adapter_crash` and wiped the session's loaded data, and
31
+ because the MCP layer swallows the exception the kill was invisible in the
32
+ tool response. Busy is now a distinct `_WorkerBusy` signal (a
33
+ `_WorkerTimeout` subclass, so warn-only callers are unaffected) that never
34
+ kills.
35
+ - **A timed-out read no longer steals the next request's response.** Each
36
+ request spawned its own `readline()` thread and abandoned it on deadline
37
+ overrun; the orphan stayed blocked on the pipe and consumed the *following*
38
+ response. The next `execute()` then burned its entire timeout (default
39
+ 600 s) waiting for a reply that had already been eaten, after which the
40
+ pool killed a perfectly healthy worker. Reads now come off one long-lived
41
+ pump thread per worker, which cannot orphan, and responses carrying a
42
+ stale request id are discarded rather than mis-matched.
43
+ - **`send_simple_op` honors its full timeout budget.** The read deadline was
44
+ computed *after* acquiring the worker lock, so a call that spent most of
45
+ its budget waiting for the lock got a second full budget for the read —
46
+ worst case 2x `timeout_ms`, and `list_session_info_detailed` iterates
47
+ workers serially, so N busy workers stalled the status tool for
48
+ `2 * timeout * N`. The deadline is now fixed up front.
49
+ - **Repeated identical notes are no longer double-counted.** The previous
50
+ de-dup short-circuited before recording the matched span, so the second and
51
+ later copies of an identical `note:` line went unclaimed and were picked up
52
+ again by the generic-note pass — the exact double-count the fix was meant
53
+ to remove, reachable from any `foreach` loop re-running the same
54
+ regression.
55
+ - **`stata_info`'s worker eviction is identity-checked.** It popped the
56
+ session's worker unconditionally, so a concurrent `execute()` that had
57
+ already noticed the death and registered a replacement got *its* worker
58
+ evicted — leaking a live pystata subprocess that `request_cancel` /
59
+ `kill_session` / `shutdown` could no longer reach.
60
+ - **`kill()`'s already-dead fast path is identity-checked** too, matching its
61
+ own slow path: it could otherwise clear a handle a concurrent `execute()`
62
+ had just respawned into, stranding a live subprocess mid-request.
63
+ - **`TypeError` is no longer blanket-classified as a caller error.** The
64
+ guard wrapped the whole `execute()` call, not just argument binding, so a
65
+ `TypeError` raised deep inside result collection told the agent its
66
+ arguments were wrong when nothing was — and routed the failure out of the
67
+ `adapter_crash` path that would have recycled the wedged worker. Malformed
68
+ options are now caught by signature binding before the call; deeper
69
+ `TypeError`s stay in the `worker_error` bucket.
70
+ - **Session ids longer than Stata's 32-character name cap are mapped.** They
71
+ were passed through verbatim as a frame name, so `frame create` failed with
72
+ rc 198; they now route through the hashed mapped-frame path.
73
+
74
+ - **Worker stderr pipe is now drained continuously.** The subprocess pool
75
+ only read a worker's stderr after death, so a worker whose cumulative
76
+ stderr output (pystata banners, Stata C-side messages, warnings) exceeded
77
+ the ~64 KB OS pipe buffer blocked mid-write and never responded — the
78
+ parent then misreported the healthy worker as a `timeout` and killed it
79
+ (or hung forever with `timeout_ms=None`). A per-worker daemon thread now
80
+ drains stderr into a bounded tail buffer, which also enriches crash
81
+ messages.
82
+ - **A worker-reported failure no longer destroys the session.** A live
83
+ worker answering `{ok:false, error_kind:"worker_error"}` was handled like
84
+ a dead process: killed and dropped from the pool, wiping the session's
85
+ loaded dataset and `r()`/`e()` state — reachable from a mere argument
86
+ type typo. Such failures now surface as structured errors while the
87
+ worker (and the session's data) survives; the worker also classifies
88
+ `TypeError` as `invalid_request`.
89
+ - **LRU eviction skips busy workers.** `last_used` is stale for a mid-run
90
+ worker, so capacity pressure from an unrelated new session preferred the
91
+ longest-running request as its eviction victim (SIGTERM mid-run →
92
+ `adapter_crash`, session data lost). Eviction now skips in-flight
93
+ workers, bumps `last_used` at request start, and kills victims outside
94
+ the pool lock; timeout/error cleanup no longer removes a newer worker
95
+ handle registered in the interim.
96
+ - **Status queries no longer hang behind a long run.** `send_simple_op`
97
+ blocked unboundedly on the worker lock held by an in-flight `execute()`;
98
+ lock acquisition now counts against the same timeout budget and surfaces
99
+ as a per-session "busy" warning.
100
+ - **`rc` is taken from the last `r(NNN);` in a failure transcript.** An
101
+ earlier successful command echoing a literal `r(NNN);` (display string,
102
+ help output) previously hijacked the return code — and therefore the
103
+ `ErrorKind` classification, suggestions, and recovery contract.
104
+ - **Indented notes are no longer double-counted as warnings.** An indented
105
+ `note: … omitted because of collinearity` line produced both an
106
+ `omitted_collinear` and a generic `note` warning.
107
+ - **A session literally named `"default"` no longer aliases `"main"`.** In
108
+ in-process mode it silently shared `main`'s Stata frame (one dataset for
109
+ two nominally distinct sessions); it now routes through the private
110
+ mapped-frame path.
111
+ - **Matrices with missing sfi row/col names are no longer dropped.**
112
+ Positional names (`r1…`/`c1…`) are synthesized instead of letting the
113
+ shape validator silently discard successfully read values.
114
+ - **`get_graph(format=…)` is no longer a silent no-op.** Requesting a
115
+ format different from the stored one now returns `invalid_request`
116
+ instead of returning mismatched bytes.
117
+ - **Post-run file snapshots survive escaping symlinks.** A symlink inside
118
+ the working dir pointing outside it crashed `changed_output_files` with
119
+ an uncaught `ValueError`; it is now skipped.
120
+
121
+ ### Added
122
+
123
+ - **≈300 new offline tests** (467 → 724 passing without Stata); coverage
124
+ 73% → 90% overall (`mcp/server` 74→99%, `kernel` 73→98%, `_pool` 68→96%,
125
+ `_refs` 68→100%, `log_artifacts` 76→96%, `runner` 29→72%). The package is
126
+ now fully mypy-clean, including the previously unchecked top-level
127
+ modules.
128
+ - **Community health files**: `CONTRIBUTING.md` (dev setup + the exact CI
129
+ gates), `CODE_OF_CONDUCT.md` (Contributor Covenant 2.1), `SECURITY.md`,
130
+ and GitHub issue templates that ask for `stata-code doctor` output.
131
+ - **Second-round regression tests** for the eight fixes listed above, each
132
+ verified to fail against the pre-fix source (823 passing locally with
133
+ Stata). They drive real subprocess workers through `SessionPool` — a
134
+ status query racing a live run, a worker that drops its first request, a
135
+ worker that releases the lock partway — rather than asserting on a mocked
136
+ seam, which is what let the first round's regressions through.
137
+
138
+ ### Docs
139
+
140
+ - Regenerated the skill's `rc → kind` reference from `errors.py` — it still
141
+ documented the pre-0.9.0 mappings the taxonomy audit had corrected; fixed
142
+ the "32 kinds" claims (the enum has 31).
143
+ - `plugin.json` install verify no longer uses `stata-code-mcp --help`
144
+ (which hangs); it runs `stata-code doctor --no-stata-probe`.
145
+ - PUBLISHING.md / AGENTS.md version-bump lists now cover all eight version
146
+ literals the release gate checks; removed the stale claim that a built
147
+ `.vsix` ships in the repo.
148
+ - SCHEMA.md documents `EstimationResult.command_family` and `.diagnostics`;
149
+ status lines updated v0.8 → v0.9; README.zh.md re-synced with the English
150
+ README (plugin-marketplace install, per-client MCP config table, Open VSX
151
+ and first-activation notes, cell/section conventions).
152
+
153
+ ## 0.9.0 — 2026-06-23
154
+
155
+ ### Fixed
156
+
157
+ - **Error-taxonomy correctness.** Audited the `_rc` → `ErrorKind` table against
158
+ StataCorp's `[P] error` manual (Stata 19) and corrected several
159
+ misclassifications: `not_sorted` is now `r(5)` (was the unrelated `r(119)`
160
+ "statement out of context" / `r(459)` "data is not…"); numlist errors
161
+ `r(122)`/`r(123)` are now `syntax` (were `invalid_name`); `r(322)` and
162
+ `r(1400)` map to `estimation_failure` (was `file_not_found` /
163
+ `estimation_sample_empty`); `r(480)` maps to `infeasible` (was
164
+ `out_of_memory`); local I/O `r(691)`–`r(693)` map to `file_io` (were
165
+ `network`). Misleading mappings for `r(9)`/`r(604)`/`r(615)`/`r(616)` were
166
+ removed (they fall through to `unknown` rather than assert a wrong kind).
167
+ - **Command "did you mean?" now fires.** The `command_not_found` (rc 199) name
168
+ extractor expected `"<X> unrecognized command"`, but Stata's actual message is
169
+ `"command <X> is unrecognized"` — so the fuzzy suggestion never matched in
170
+ practice (synthetic unit tests passed the name in directly and hid it). Fixed
171
+ the regex and added a real-Stata integration test so a typo like `regresss`
172
+ now surfaces "Did you mean `regress`?".
173
+
174
+ ### Added
175
+
176
+ - **Typed estimation contract.** `RunResult.results.estimation` now exposes a
177
+ frontend-neutral coefficient table derived from verified `r(table)` when
178
+ possible, or from inline `e(b)` / `e(V)` as a clearly marked fallback. New
179
+ public helpers `build_estimation_result()` and
180
+ `build_estimation_from_returns()` keep the contract unit-testable without
181
+ Stata. The contract also carries a coarse `command_family`
182
+ (ols/iv/gmm/panel/count/did/…) and command-aware `diagnostics` — identification
183
+ and specification tests surfaced from `e()` for the commands economists must
184
+ report (`ivreg2`/`ivreghdfe` weak-ID F and Hansen J, `xtabond2` AR(2)/Hansen,
185
+ `reghdfe` within-R²/absorbed FE, `xtreg` rho). Only scalars actually present in
186
+ `e()` are surfaced — never fabricated.
187
+ - **Machine-readable recovery contract.** `error.recovery` now classifies each
188
+ `ErrorKind` by failure domain and tells agents whether an unchanged retry,
189
+ code edit, or user/out-of-band action is likely needed. Synthetic timeout,
190
+ cancellation, and adapter-crash errors carry the same recovery metadata as
191
+ ordinary Stata errors.
192
+ - **Reproducibility provenance helpers.** New `Provenance`,
193
+ `build_provenance()`, and `build_reproducible_do()` helpers turn a completed
194
+ `RunResult` plus original code into a runtime provenance envelope and a
195
+ re-runnable `.do` script preamble with Stata `version`, `set more off`, and an
196
+ optional `set seed`. Provenance now also records **per-package dependencies**
197
+ parsed from the script (`extract_package_installs()` →
198
+ `Provenance.packages`: `ssc`/`net install` name, source, and `from()` URL),
199
+ and `build_submission_package()` assembles a self-contained
200
+ replication/journal-submission bundle (`analysis.do` + `PROVENANCE.json` +
201
+ a `README.md` manifest listing runtime, seed, and required community packages).
202
+ - **Data-MCP handoff verifier.** New `verify_dataset()` and `DatasetCheck`
203
+ helpers validate imported datasets against provider metadata such as expected
204
+ row count, variable count, observation bounds, and required variables.
205
+ - **`error.rc_label` is now populated for real Stata errors.** New
206
+ `RC_LABEL` table and `label_for_rc()` (public API) supply Stata's canonical
207
+ short message (e.g. `r(111)` → "variable not found") so agents have a stable,
208
+ transcript-independent descriptor to branch and group on. Unverified codes
209
+ yield an empty label rather than a guess.
210
+ - **More return codes classified** (shrinking `unknown`): real network codes
211
+ `r(2)`/`r(631)`/`r(672)`/`r(677)` → `network`; `r(688)` → `file_corrupt`;
212
+ `r(907)` → `stata_limit`; `r(950)` → `out_of_memory`; numlist `r(124)`–`r(127)`
213
+ → `syntax`.
214
+ - **Remediation suggestions for more error kinds.** `suggestions_for()` now
215
+ emits actionable hints for `network`, `infeasible`, `type_mismatch`,
216
+ `file_io`, `file_corrupt`, `permission`, `estimation_failure`, and
217
+ `matrix_missing`, so nearly every common failure ships a recovery hint.
218
+
7
219
  ## 0.8.1 — 2026-06-20
8
220
 
9
221
  ### Changed
@@ -160,8 +372,8 @@ to semver-major.minor for the result schema (see `SCHEMA.md` §6).
160
372
 
161
373
  ### Added
162
374
 
163
- - **Claude Code plugin marketplace manifest.** `.claude-plugin/marketplace.json`
164
- + `.claude-plugin/plugin.json` expose the repo as a single-plugin
375
+ - **Claude Code plugin marketplace manifest.** `.claude-plugin/marketplace.json` +
376
+ `.claude-plugin/plugin.json` expose the repo as a single-plugin
165
377
  marketplace, so users can install everything (MCP server config + agent
166
378
  skill) with `claude plugin marketplace add brycewang-stanford/stata-code`
167
379
  followed by `claude plugin install stata-code`.
@@ -602,6 +814,9 @@ Aggregated from the prior `Unreleased` section; covers 0.6.1 and 0.6.2.
602
814
  `vscode/package.json`, and the VSCode MCP-client handshake all
603
815
  declare `0.3.0`.
604
816
 
817
+ - **MCP server tool count is now 8** (added `get_matrix`,
818
+ `cancel_session`).
819
+
605
820
  ### Added
606
821
 
607
822
  - **VSCode extension v0.3 — full UI surface** (`vscode/`). Beyond the
@@ -684,11 +899,6 @@ Aggregated from the prior `Unreleased` section; covers 0.6.1 and 0.6.2.
684
899
  in-process and has no clean cancel primitive). Hard interruption
685
900
  remains deferred to the subprocess-based runtime planned for v0.3+.
686
901
 
687
- ### Changed
688
-
689
- - **MCP server tool count is now 8** (added `get_matrix`,
690
- `cancel_session`).
691
-
692
902
  ## [0.2.0] — 2026-05-07
693
903
 
694
904
  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.8.1
3
+ Version: 0.9.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
@@ -118,7 +118,7 @@ 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
+ **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**.
122
122
 
123
123
  Three workflows the current tree explicitly supports for end users and agents:
124
124
 
@@ -184,12 +184,14 @@ Verify the local setup with the read-only doctor:
184
184
  stata-code doctor
185
185
  stata-code doctor --json # machine-readable output
186
186
  stata-code doctor --no-stata-probe # skip live Stata initialization
187
+ stata-code doctor --workspace /path/to/project --no-user-config-scan
187
188
  ```
188
189
 
189
190
  The doctor reports the package/Python version, MCP and Jupyter extras, `pystata`
190
- discovery, console scripts on `PATH`, client/VS Code configuration hints, and a
191
- best-effort Stata version/edition probe. It never edits shell, Stata, Claude, or
192
- VS Code config.
191
+ discovery, console scripts on `PATH`, common project/user MCP client config
192
+ files, client/VS Code configuration hints, and a best-effort Stata
193
+ version/edition probe. It never edits shell, Stata, Claude, Cursor, or VS Code
194
+ config.
193
195
 
194
196
  ---
195
197
 
@@ -418,7 +420,9 @@ If the extension or an MCP client cannot find the server, run
418
420
  `stata-code doctor --no-stata-probe` in the same Python environment. It reports
419
421
  whether `stata-code-mcp` is on `PATH` and suggests absolute-path or
420
422
  `python -m stata_code.mcp` fallbacks for GUI clients whose `PATH` differs from
421
- your shell.
423
+ your shell. It also reads common MCP config files in the current workspace and
424
+ user config directories so you can see whether a client is already wired to
425
+ `stata-code`.
422
426
 
423
427
  #### Cell and section conventions
424
428
 
@@ -561,10 +565,17 @@ The `stata_required` marker tags the real-Stata integration tests. CI uses `pyte
561
565
 
562
566
  ## Contributing
563
567
 
568
+ Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for the
569
+ development setup, the checks CI runs, and PR guidelines. In short:
570
+
564
571
  - Read [LICENSE-POLICY.md](LICENSE-POLICY.md) before opening a PR.
565
572
  - Add a one-line acknowledgement to your first PR description; the template is in the policy file.
566
573
  - Tests are required for any new schema field or runner behavior.
567
574
 
575
+ Bug reports and support questions go through
576
+ [GitHub issues](https://github.com/brycewang-stanford/stata-code/issues);
577
+ security reports go through [SECURITY.md](SECURITY.md).
578
+
568
579
  ---
569
580
 
570
581
  ## License
@@ -20,12 +20,12 @@ Trusted Publishing has two modes:
20
20
  - **Pending publisher** (preferred for the first release of a brand-new
21
21
  project): you configure the trusted publisher *before* the project exists
22
22
  on PyPI, and the first successful publish creates it. Go to
23
- https://pypi.org/manage/account/publishing/ and add a "pending publisher"
23
+ <https://pypi.org/manage/account/publishing/> and add a "pending publisher"
24
24
  with the values listed in step 2.
25
25
  - **Existing project**: if you already pushed an initial release manually
26
26
  (e.g. via `twine upload` with a one-off API token), configure trusted
27
27
  publishing on the project page at
28
- https://pypi.org/manage/project/stata-code/settings/publishing/.
28
+ <https://pypi.org/manage/project/stata-code/settings/publishing/>.
29
29
 
30
30
  Either path lands you in the same place after the first run.
31
31
 
@@ -76,30 +76,39 @@ The environment doesn't need any secrets — Trusted Publishing handles auth.
76
76
 
77
77
  Once the one-time setup above is done, every release is just:
78
78
 
79
- 1. **Bump the version everywhere**:
79
+ 1. **Bump the version everywhere** (eight literals across six files —
80
+ `scripts/check_versions.py` gates the release build on all of them):
80
81
  - `pyproject.toml` → `[project] version`
81
82
  - `stata_code/__init__.py` → `__version__`
82
83
  - `stata_code/mcp/server.py` → `__version__`
83
84
  - `vscode/package.json` → `version`
84
- - `vscode/package-lock.json` → root package `version`
85
+ - `vscode/package-lock.json` → top-level `version` **and** `packages[""].version`
86
+ - `.claude-plugin/plugin.json` → `version`
87
+ - `.claude-plugin/marketplace.json` → `metadata.version` **and** each `plugins[*].version`
85
88
  2. **Run the version guard** before tagging:
89
+
86
90
  ```bash
87
91
  python scripts/check_versions.py
88
92
  ```
93
+
89
94
  3. **Update `CHANGELOG.md`**: move the `[Unreleased]` entries under a new
90
95
  `## [X.Y.Z] - YYYY-MM-DD` heading, leaving an empty `[Unreleased]` shell
91
96
  on top.
92
97
  4. **Commit** the bump:
98
+
93
99
  ```bash
94
100
  git add pyproject.toml stata_code/__init__.py stata_code/mcp/server.py vscode/package.json vscode/package-lock.json CHANGELOG.md
95
101
  git commit -m "release: vX.Y.Z"
96
102
  ```
103
+
97
104
  5. **Tag and push**:
105
+
98
106
  ```bash
99
107
  git tag vX.Y.Z
100
108
  git push origin main
101
109
  git push origin vX.Y.Z
102
110
  ```
111
+
103
112
  6. Watch the **`release` workflow** under the Actions tab. It will:
104
113
  - build the sdist + wheel
105
114
  - run `twine check` on the artifacts
@@ -127,14 +136,14 @@ version if a publish has already succeeded under that number.
127
136
 
128
137
  ## Manual / emergency publish
129
138
 
130
- `release.yml` also accepts `workflow_dispatch`, so you can trigger a build
131
- + publish from the Actions UI without pushing a tag. The `github-release`
139
+ `release.yml` also accepts `workflow_dispatch`, so you can trigger a build +
140
+ publish from the Actions UI without pushing a tag. The `github-release`
132
141
  job will fail in that mode (no tag to attach to) — that's fine; the PyPI
133
142
  publish itself will have already happened in `publish-pypi`.
134
143
 
135
144
  If Trusted Publishing is broken for some reason and you need an emergency
136
145
  release, generate a short-lived API token at
137
- https://pypi.org/manage/account/token/ and run locally:
146
+ <https://pypi.org/manage/account/token/> and run locally:
138
147
 
139
148
  ```bash
140
149
  python -m build
@@ -79,7 +79,7 @@ Then just ask:
79
79
  └─────────────┘ └────────────┘ └─────────────────┘
80
80
  ```
81
81
 
82
- **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**.
82
+ **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**.
83
83
 
84
84
  Three workflows the current tree explicitly supports for end users and agents:
85
85
 
@@ -145,12 +145,14 @@ Verify the local setup with the read-only doctor:
145
145
  stata-code doctor
146
146
  stata-code doctor --json # machine-readable output
147
147
  stata-code doctor --no-stata-probe # skip live Stata initialization
148
+ stata-code doctor --workspace /path/to/project --no-user-config-scan
148
149
  ```
149
150
 
150
151
  The doctor reports the package/Python version, MCP and Jupyter extras, `pystata`
151
- discovery, console scripts on `PATH`, client/VS Code configuration hints, and a
152
- best-effort Stata version/edition probe. It never edits shell, Stata, Claude, or
153
- VS Code config.
152
+ discovery, console scripts on `PATH`, common project/user MCP client config
153
+ files, client/VS Code configuration hints, and a best-effort Stata
154
+ version/edition probe. It never edits shell, Stata, Claude, Cursor, or VS Code
155
+ config.
154
156
 
155
157
  ---
156
158
 
@@ -379,7 +381,9 @@ If the extension or an MCP client cannot find the server, run
379
381
  `stata-code doctor --no-stata-probe` in the same Python environment. It reports
380
382
  whether `stata-code-mcp` is on `PATH` and suggests absolute-path or
381
383
  `python -m stata_code.mcp` fallbacks for GUI clients whose `PATH` differs from
382
- your shell.
384
+ your shell. It also reads common MCP config files in the current workspace and
385
+ user config directories so you can see whether a client is already wired to
386
+ `stata-code`.
383
387
 
384
388
  #### Cell and section conventions
385
389
 
@@ -522,10 +526,17 @@ The `stata_required` marker tags the real-Stata integration tests. CI uses `pyte
522
526
 
523
527
  ## Contributing
524
528
 
529
+ Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for the
530
+ development setup, the checks CI runs, and PR guidelines. In short:
531
+
525
532
  - Read [LICENSE-POLICY.md](LICENSE-POLICY.md) before opening a PR.
526
533
  - Add a one-line acknowledgement to your first PR description; the template is in the policy file.
527
534
  - Tests are required for any new schema field or runner behavior.
528
535
 
536
+ Bug reports and support questions go through
537
+ [GitHub issues](https://github.com/brycewang-stanford/stata-code/issues);
538
+ security reports go through [SECURITY.md](SECURITY.md).
539
+
529
540
  ---
530
541
 
531
542
  ## License
@@ -77,7 +77,38 @@ Every successful or failed Stata execution returns one result object:
77
77
  }
78
78
  }
79
79
  },
80
- "last_estimation_cmd": "regress"
80
+ "last_estimation_cmd": "regress",
81
+ "estimation": {
82
+ "command": "regress",
83
+ "depvar": "mpg",
84
+ "n_obs": 74,
85
+ "df_model": 1,
86
+ "df_resid": null,
87
+ "statistic_kind": "z",
88
+ "source": "e_b_v",
89
+ "ci_level": 95.0,
90
+ "coefficients": [
91
+ {
92
+ "term": "weight",
93
+ "b": -0.006,
94
+ "se": null,
95
+ "statistic": null,
96
+ "p_value": null,
97
+ "ci_low": null,
98
+ "ci_high": null
99
+ },
100
+ {
101
+ "term": "_cons",
102
+ "b": 39.44,
103
+ "se": null,
104
+ "statistic": null,
105
+ "p_value": null,
106
+ "ci_low": null,
107
+ "ci_high": null
108
+ }
109
+ ],
110
+ "model_stats": {"N": 74, "df_m": 1, "r2": 0.219}
111
+ }
81
112
  },
82
113
 
83
114
  "dataset": {
@@ -140,7 +171,8 @@ A failed execution sets `ok: false`, `rc != 0`, and populates `error`:
140
171
 
141
172
  "results": { "r": {"scalars": {}, "macros": {}, "matrices": {}},
142
173
  "e": {"scalars": {}, "macros": {}, "matrices": {}},
143
- "last_estimation_cmd": null },
174
+ "last_estimation_cmd": null,
175
+ "estimation": null },
144
176
 
145
177
  "dataset": { "frame": "default", "n_obs": 74, "n_vars": 12, "changed": false,
146
178
  "filename": "auto.dta", "variables": null },
@@ -167,7 +199,13 @@ A failed execution sets `ok: false`, `rc != 0`, and populates `error`:
167
199
  "suggestions": [
168
200
  {"action": "Check the variable name. Did you mean `mpg`?",
169
201
  "command": "describe"}
170
- ]
202
+ ],
203
+ "recovery": {
204
+ "category": "user_code",
205
+ "retriable": false,
206
+ "needs_code_change": true,
207
+ "needs_user_input": false
208
+ }
171
209
  },
172
210
 
173
211
  "schema_version": "1.0",
@@ -315,6 +353,36 @@ Stata's `r()` and `e()` return dictionaries, structurally separated. Each follow
315
353
  | Field | Type | Notes |
316
354
  | --- | --- | --- |
317
355
  | `last_estimation_cmd` | `string \| null` | Mirrors `e(cmd)` for callers who don't want to dig into `e.macros`. After multi-command code, this reflects the *last* command that wrote to `e()`. `null` if no estimation has been performed. |
356
+ | `estimation` | `EstimationResult \| null` | Typed coefficient table derived from `r(table)` or `e(b)` / `e(V)`. `null` when no inline `e(b)` is available. |
357
+
358
+ **`EstimationResult` shape:**
359
+
360
+ | Field | Type | Notes |
361
+ | --- | --- | --- |
362
+ | `command` | `string \| null` | Mirrors `e(cmd)` when available; falls back to `last_estimation_cmd`. |
363
+ | `command_family` | `string \| null` | Coarse estimator family derived from the command name (`ols` / `iv` / `gmm` / `panel` / `count` / `did` / …); `null` when the command is unrecognized. |
364
+ | `depvar` | `string \| null` | Mirrors `e(depvar)`. |
365
+ | `n_obs` | `int \| null` | Integer form of `e(N)` when available. |
366
+ | `df_model` | `number \| null` | Mirrors `e(df_m)`. |
367
+ | `df_resid` | `number \| null` | Mirrors `e(df_r)`. |
368
+ | `statistic_kind` | `"t" \| "z"` | Which statistic fills each coefficient's `statistic` field. |
369
+ | `source` | `"r_table" \| "e_b_v"` | `r_table` means values were copied from Stata's displayed `r(table)` after verifying its columns and `b` row match `e(b)`; `e_b_v` means point estimates come from `e(b)` and inference, when present, is computed from `e(V)` with a normal approximation. |
370
+ | `ci_level` | `number` | Confidence level used for `ci_low` / `ci_high`; currently `95.0`. |
371
+ | `coefficients` | `array<Coefficient>` | One row per term in `e(b)`. |
372
+ | `model_stats` | `dict<str, number \| null>` | High-signal subset of `e()` scalars such as `N`, `df_m`, `df_r`, `r2`, `F`, `chi2`, `ll`, and `rmse`. Full scalars remain under `results.e.scalars`. |
373
+ | `diagnostics` | `dict<str, number \| null>` | Command-aware identification/specification statistics surfaced from `e()` (e.g. weak-ID F and Hansen J for `ivreg2`/`ivreghdfe`, AR(2)/Hansen for `xtabond2`, within-R² for `reghdfe`, `rho` for `xtreg`). Only scalars actually present in `e()` appear — never fabricated. |
374
+
375
+ **`Coefficient` shape:**
376
+
377
+ | Field | Type | Notes |
378
+ | --- | --- | --- |
379
+ | `term` | `string` | Term / coefficient column name. |
380
+ | `b` | `number \| null` | Point estimate. |
381
+ | `se` | `number \| null` | Standard error when available. |
382
+ | `statistic` | `number \| null` | `t` or `z`, per `EstimationResult.statistic_kind`. |
383
+ | `p_value` | `number \| null` | Two-sided p-value when available. |
384
+ | `ci_low` | `number \| null` | Lower confidence interval bound when available. |
385
+ | `ci_high` | `number \| null` | Upper confidence interval bound when available. |
318
386
 
319
387
  **Empty is empty.** Sub-dicts are `{}` when Stata returned nothing — never absent, never `null`.
320
388
 
@@ -377,6 +445,7 @@ Populated iff `ok: false`. The schema's most important contribution to agent UX:
377
445
  | `varname` | `string \| null` | For `varname_not_found` and related, the variable name at issue. |
378
446
  | `name` | `string \| null` | For `name_conflict` and `invalid_name`, the conflicting/invalid name. |
379
447
  | `suggestions` | `array<Suggestion>` | Producer-supplied remediation hints. Empty when none apply. See below. |
448
+ | `recovery` | `Recovery \| null` | Machine-readable recovery contract for agents. Present on current producers; old or third-party producers may omit it, so consumers should handle `null`. |
380
449
 
381
450
  **`context` shape:**
382
451
 
@@ -399,36 +468,47 @@ Populated iff `ok: false`. The schema's most important contribution to agent UX:
399
468
 
400
469
  Suggestions are best-effort; agents should treat them as hints, not directives. A suggestion is not consent to mutate source files or silently retry changed code; consumers should apply fixes automatically only in workflows where the user requested repair or approved iteration. The `kind` enum below documents what suggestions are typically populated.
401
470
 
471
+ **`Recovery` shape:**
472
+
473
+ | Field | Type | Notes |
474
+ | --- | --- | --- |
475
+ | `category` | `"user_code" \| "data" \| "model" \| "resource" \| "environment" \| "internal" \| "unknown"` | Broad failure domain for routing. |
476
+ | `retriable` | `bool` | Whether re-running the exact same code may succeed. True mainly for transient environment or producer-side failures. |
477
+ | `needs_code_change` | `bool` | Whether the submitted Stata code must change to succeed. |
478
+ | `needs_user_input` | `bool` | Whether resolution likely requires a human or out-of-band action such as permissions, license/edition limits, or re-acquiring a corrupt file. |
479
+
402
480
  **`kind` enum (v1.0):**
403
481
 
482
+ rc(s) below cite StataCorp `[P] error` (Stata 19, 2025). The code is authoritative; this table is a readable mirror.
483
+
404
484
  | `kind` | Typical rc(s) | Notes / suggestion seed |
405
485
  | --- | --- | --- |
406
- | `syntax` | 9, 100, 101, 102, 103, 121, 130, 132, 197, 198 | Generic parser failure. No automatic suggestion. |
486
+ | `syntax` | 100, 101, 102, 103, 121–127, 130, 132, 197, 198 | Generic parser failure (incl. numlist errors 121–127). No automatic suggestion. |
407
487
  | `command_not_found` | 199 | Often resolved by `ssc install` or `net install`; suggestions populated when Stata reports a likely package name. |
408
488
  | `varname_not_found` | 111 | `varname` populated. Suggestions may include similar varnames from `dataset.variables`. |
409
- | `invalid_name` | 122, 123 | `name` populated. |
410
- | `type_mismatch` | 109, 408 | |
489
+ | `invalid_name` | (no dedicated rc) | Stata folds "invalid name" into r(198). `name` populated when constructed by a producer. |
490
+ | `type_mismatch` | 109, 408 | Suggestion: `destring`/`tostring`. |
411
491
  | `name_conflict` | 110 | `name` populated. Suggestion typically: `replace`. |
412
- | `not_sorted` | 119, 459 | Suggestion: `sort <varlist>`. |
492
+ | `not_sorted` | 5 | Suggestion: `sort <varlist>`. |
413
493
  | `convergence` | 430 | |
414
- | `infeasible` | 491 | Distinct from convergence: starting values not feasible. |
415
- | `estimation_sample_empty` | 1400, 2000 (in estimation context) | |
416
- | `estimation_failure` | 1401, 1402 | |
494
+ | `infeasible` | 480, 491 | Distinct from convergence: starting values not feasible (e.g. `nl`, `ml`). |
495
+ | `estimation_sample_empty` | (no dedicated rc) | Empty estimation samples surface as r(2000); producer-set otherwise. |
496
+ | `estimation_failure` | 322, 1400, 1401, 1402 | Postestimation/prefix saw an unexpected result, or numerical overflow. |
417
497
  | `no_estimation_results` | 301 | Common when calling `predict`/`margins` without prior estimation. |
418
498
  | `no_observations` | 2000, 2001 | |
419
499
  | `data_in_memory` | 4 | Suggestion: `clear`. |
420
500
  | `matrix_singular` | 506, 508 | Matrix not positive definite / not invertible. |
421
- | `matrix_conformability` | 503, 507 | Dimension mismatch. |
501
+ | `matrix_conformability` | 503, 507 | Dimension mismatch; 507 is a `matrix post` row/col name conflict kept in the matrix bucket. |
422
502
  | `matrix_missing` | 504 | Matrix has missing values. |
423
- | `file_not_found` | 322, 601 | `path` populated. |
503
+ | `file_not_found` | 601 | `path` populated. |
424
504
  | `file_exists` | 602 | `path` populated. Suggestion: pass `replace` option. |
425
- | `file_corrupt` | 604, 610 | `path` populated. Often "not a Stata file." |
426
- | `file_io` | 603, 691 (local) | `path` populated. Catch-all for open/read/write failures not otherwise classified. |
427
- | `network` | 691 (network), 692, 693 | URL fetches, network reads. |
428
- | `permission` | 608 | `path` populated. Includes Stata-license-limit errors (615/616 family that surface as permission denials). |
429
- | `encoding` | 615, 616 | Unicode / encoding-conversion failures. |
430
- | `stata_limit` | 901, 902, 903 | Edition / matsize / similar Stata-imposed caps. Distinct from OS OOM. Suggestion: `set maxvar` or upgrade edition. |
431
- | `out_of_memory` | 480, 909 | OS-level memory exhaustion. |
505
+ | `file_corrupt` | 610, 688 | `path` populated. "Not a Stata file" (610) or genuinely corrupt (688). |
506
+ | `file_io` | 603, 691, 692, 693 | `path` populated. Catch-all for open/read/write failures (691–693 are local filesystem I/O). |
507
+ | `network` | 2, 631, 672, 677 | Connection timed out / host not found / server refused / remote connection failed. |
508
+ | `permission` | 608 | `path` populated. File is read-only / not writable. |
509
+ | `encoding` | (no dedicated rc) | Unicode / encoding-conversion failures; producer-set. |
510
+ | `stata_limit` | 901, 902, 903, 907 | Edition / maxvar / width caps. Distinct from OS OOM. Suggestion: `set maxvar` or upgrade edition. |
511
+ | `out_of_memory` | 909, 950 | OS-level memory exhaustion. Suggestion: `compress`. |
432
512
  | `interrupt` | 1 | User Break / Ctrl-C from a frontend. |
433
513
  | `cancelled` | (synthetic `rc: -3`) | Cancellation was requested. Subprocess-backed producers may terminate an in-flight worker; the direct in-process runner only short-circuits before Stata receives code. |
434
514
  | `timeout` | (synthetic `rc: -2`) | Adapter-imposed time limit exceeded. |
@@ -607,17 +687,20 @@ This section tracks how much of the schema is wired up in code. Not normative
607
687
  emit a `matrix://<request_id>/<r|e>/<name>` ref instead, retrievable
608
688
  via `get_matrix(ref)`.
609
689
  - `results.last_estimation_cmd` (mirrors `e(cmd)`).
690
+ - `results.estimation` typed coefficient table, copied from verified
691
+ `r(table)` when possible and otherwise derived from inline `e(b)` / `e(V)`.
610
692
  - `dataset` block — `n_obs`, `n_vars`, `frame`, `changed`, `filename`,
611
693
  and `variables` (capped at 200 entries).
612
694
  - `graphs[]` with `ref` + on-disk capture pipeline; format restricted to
613
695
  `png` / `svg` / `pdf`; PNG `width` / `height` parsed from IHDR;
614
696
  best-effort `source_command` / `source_line` attribution from the
615
697
  submitted code. `inline` populated when `include_graphs="inline"`.
616
- - Structured `error` — 32-kind enum, `varname` / `path` / `name`
698
+ - Structured `error` — 31-kind enum, `varname` / `path` / `name`
617
699
  extracted from Stata's English error text by regex, structured
618
700
  `context` (`{before, failing, after}`), `commands_executed` parsed
619
701
  from pystata's multi-line transcript, `suggestions` generated by
620
- `core.errors.suggestions_for`.
702
+ `core.errors.suggestions_for`, and `recovery` generated by
703
+ `core.errors.recovery_for`.
621
704
  - `request_id` (uuid4 hex), `started_at` (ISO 8601 UTC ms),
622
705
  `stata_elapsed_ms`, `capabilities`.
623
706
  - Multi-session via Stata frames — `session_id="main"` ↔ `default`