stata-code 0.3.1__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. {stata_code-0.3.1 → stata_code-0.4.0}/.gitignore +1 -0
  2. {stata_code-0.3.1 → stata_code-0.4.0}/CHANGELOG.md +61 -1
  3. {stata_code-0.3.1 → stata_code-0.4.0}/PKG-INFO +37 -11
  4. {stata_code-0.3.1 → stata_code-0.4.0}/README.md +36 -10
  5. {stata_code-0.3.1 → stata_code-0.4.0}/SCHEMA.md +40 -0
  6. {stata_code-0.3.1 → stata_code-0.4.0}/pyproject.toml +1 -1
  7. {stata_code-0.3.1 → stata_code-0.4.0}/schema/run_result.schema.json +126 -0
  8. {stata_code-0.3.1 → stata_code-0.4.0}/stata_code/__init__.py +3 -1
  9. {stata_code-0.3.1 → stata_code-0.4.0}/stata_code/core/__init__.py +2 -0
  10. stata_code-0.4.0/stata_code/core/log_artifacts.py +431 -0
  11. {stata_code-0.3.1 → stata_code-0.4.0}/stata_code/core/runner.py +145 -4
  12. {stata_code-0.3.1 → stata_code-0.4.0}/stata_code/core/schema.py +19 -0
  13. {stata_code-0.3.1 → stata_code-0.4.0}/stata_code/mcp/server.py +57 -1
  14. stata_code-0.4.0/tests/test_log_artifacts.py +154 -0
  15. {stata_code-0.3.1 → stata_code-0.4.0}/tests/test_mcp.py +5 -0
  16. {stata_code-0.3.1 → stata_code-0.4.0}/LICENSE +0 -0
  17. {stata_code-0.3.1 → stata_code-0.4.0}/LICENSE-POLICY.md +0 -0
  18. {stata_code-0.3.1 → stata_code-0.4.0}/PUBLISHING.md +0 -0
  19. {stata_code-0.3.1 → stata_code-0.4.0}/docs/design/hard_timeout.md +0 -0
  20. {stata_code-0.3.1 → stata_code-0.4.0}/examples/01-basic-regression.md +0 -0
  21. {stata_code-0.3.1 → stata_code-0.4.0}/examples/02-did-card-krueger.md +0 -0
  22. {stata_code-0.3.1 → stata_code-0.4.0}/examples/03-graphs.md +0 -0
  23. {stata_code-0.3.1 → stata_code-0.4.0}/examples/04-multi-session.md +0 -0
  24. {stata_code-0.3.1 → stata_code-0.4.0}/examples/05-large-matrix.md +0 -0
  25. {stata_code-0.3.1 → stata_code-0.4.0}/examples/README.md +0 -0
  26. {stata_code-0.3.1 → stata_code-0.4.0}/scripts/export_schema.py +0 -0
  27. {stata_code-0.3.1 → stata_code-0.4.0}/stata_code/core/_pool.py +0 -0
  28. {stata_code-0.3.1 → stata_code-0.4.0}/stata_code/core/_refs.py +0 -0
  29. {stata_code-0.3.1 → stata_code-0.4.0}/stata_code/core/_runtime.py +0 -0
  30. {stata_code-0.3.1 → stata_code-0.4.0}/stata_code/core/errors.py +0 -0
  31. {stata_code-0.3.1 → stata_code-0.4.0}/stata_code/kernel/__init__.py +0 -0
  32. {stata_code-0.3.1 → stata_code-0.4.0}/stata_code/kernel/__main__.py +0 -0
  33. {stata_code-0.3.1 → stata_code-0.4.0}/stata_code/kernel/kernel.py +0 -0
  34. {stata_code-0.3.1 → stata_code-0.4.0}/stata_code/mcp/__init__.py +0 -0
  35. {stata_code-0.3.1 → stata_code-0.4.0}/stata_code/mcp/__main__.py +0 -0
  36. {stata_code-0.3.1 → stata_code-0.4.0}/tests/__init__.py +0 -0
  37. {stata_code-0.3.1 → stata_code-0.4.0}/tests/fixtures/.gitkeep +0 -0
  38. {stata_code-0.3.1 → stata_code-0.4.0}/tests/test_cancel.py +0 -0
  39. {stata_code-0.3.1 → stata_code-0.4.0}/tests/test_errors.py +0 -0
  40. {stata_code-0.3.1 → stata_code-0.4.0}/tests/test_kernel.py +0 -0
  41. {stata_code-0.3.1 → stata_code-0.4.0}/tests/test_pool.py +0 -0
  42. {stata_code-0.3.1 → stata_code-0.4.0}/tests/test_runner.py +0 -0
  43. {stata_code-0.3.1 → stata_code-0.4.0}/tests/test_schema.py +0 -0
  44. {stata_code-0.3.1 → stata_code-0.4.0}/tests/test_schema_artifact.py +0 -0
@@ -218,6 +218,7 @@ __marimo__/
218
218
  .streamlit/secrets.toml
219
219
 
220
220
  # Stata-specific
221
+ log-files/
221
222
  *.gph
222
223
  *.smcl
223
224
  *.dta
@@ -4,7 +4,67 @@ 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]
7
+ ## [0.4.0] — 2026-05-07
8
+
9
+ ### Added
10
+
11
+ - **Persistent per-run log bundles.** When a `.do` file path is supplied as
12
+ `origin_path`, the runner writes an immutable `log-files/<run>/` directory
13
+ next to the source file containing:
14
+ - `<run>.log` and `<run>.smcl` — Stata's textual and SMCL logs
15
+ - `manifest.json` — run metadata (elapsed_ms, rc, session, Stata edition)
16
+ - `submitted.do` — a snapshot of the code that was executed
17
+ - `graphs/` — captured graph files materialized from graph refs
18
+ - `outputs/` — newly created or modified table/export files copied from
19
+ the run's working directory
20
+
21
+ The directory name encodes UTC timestamp, session, and request IDs so
22
+ parallel runs and reruns are never ambiguous.
23
+
24
+ - **Working-directory defaults from `origin_path`.** Before running,
25
+ Stata `cd`s to the `.do` file's parent so relative `graph export`,
26
+ `putexcel`, `esttab using`, `collect export`, etc. output next to the
27
+ source. Toggle with `use_origin_workdir` / `useDoFileDirectory` setting.
28
+ Explicit `working_dir` overrides this.
29
+
30
+ - **Schema extensions.** `LogInfo.files` (`LogFileInfo`) carries the
31
+ bundle paths and derived `graphs_dir`/`outputs_dir`; `GraphInfo.file_path`
32
+ records where a graph was materialized; two new capabilities
33
+ `log_files` and `run_artifacts` signal support.
34
+
35
+ - **MCP tool options.** `stata_run` gains `persist_log_files`,
36
+ `persist_generated_files`, `origin_path`, `origin_kind`,
37
+ `origin_label`, `use_origin_workdir`, `working_dir`.
38
+
39
+ - **VS Code settings.** Three new configuration options:
40
+ `stataCode.persistLogFiles` (default `true`),
41
+ `stataCode.persistGeneratedFiles` (default `true`),
42
+ `stataCode.useDoFileDirectory` (default `true`).
43
+
44
+ - **VS Code tree views.** The Last Result tree now shows "saved" and
45
+ "N outputs" badges on the log node when artifacts are present; the
46
+ output log header prints `working_dir:`, `log_file:`, `smcl_file:`,
47
+ `graphs_dir:`, `outputs_dir:` for each run.
48
+
49
+ ### Changed
50
+
51
+ - **VSCode MCP startup.** The extension now expands common macOS Python
52
+ script directories before spawning `stata-code-mcp`, tries workspace
53
+ `.venv` and `python -m stata_code.mcp` fallbacks for the default command,
54
+ and writes child-process stderr to the `stata-code` output channel so
55
+ missing PATH / missing dependency failures are actionable.
56
+ - **VSCode toolbar ordering.** Run-all and run-selection now share the same
57
+ ordinary `editor/title` toolbar sequence, with ordering moved later in the
58
+ `navigation` group to reduce interleaving from other extensions.
59
+
60
+ ## [0.3.2] — 2026-05-08
61
+
62
+ ### Changed
63
+
64
+ - **VSCode toolbar ordering.** Editor title-bar actions now live in one
65
+ contiguous `navigation` group so `stata-code` buttons stay together. The
66
+ order prioritizes run commands first, then data/output views, session
67
+ controls, cancellation/reset, and working-directory actions.
8
68
 
9
69
  ## [0.3.1] — 2026-05-07
10
70
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stata-code
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Agent-native Stata bridge — one core, multiple frontends (MCP, Jupyter, VSCode)
5
5
  Project-URL: Homepage, https://github.com/brycewang-stanford/stata-code
6
6
  Project-URL: Repository, https://github.com/brycewang-stanford/stata-code
@@ -135,7 +135,7 @@ else:
135
135
 
136
136
  ### As an MCP Server
137
137
 
138
- After `pip install stata-code`, the `stata-code-mcp` binary is on your `PATH`. You can wire it into Claude Code, Cursor, Claude Desktop, or any other MCP-compatible client.
138
+ After `pip install "stata-code[mcp]"`, the `stata-code-mcp` binary is on your `PATH`. You can wire it into Claude Code, Cursor, Claude Desktop, or any other MCP-compatible client.
139
139
 
140
140
  #### Claude Code via `claude mcp add` (recommended)
141
141
 
@@ -201,17 +201,30 @@ The MCP server registers 8 tools:
201
201
 
202
202
  ### As a Jupyter Kernel
203
203
 
204
+ `stata-code` ships a Jupyter kernel as part of the Python package — there is no separate "Jupyter plugin" in the JupyterLab extension marketplace. Installation is two steps: `pip install` the package with the `kernel` extra, then register the kernelspec with Jupyter.
205
+
206
+ **Prerequisites**: Stata 17+ installed locally with a valid license (the kernel calls Stata via `pystata`), and Python 3.10+ with `jupyter`/`jupyterlab` already on the same environment.
207
+
204
208
  ```bash
209
+ # 1. Install stata-code with the kernel extra (pulls in ipykernel)
210
+ pip install "stata-code[kernel]"
211
+
212
+ # 2. Register the kernelspec into Jupyter's user data dir
205
213
  stata-code-kernel install --user
214
+ # Or, equivalently:
215
+ # python -m stata_code.kernel install --user
206
216
  ```
207
217
 
208
- Or install it as a module:
218
+ Verify the kernel is registered:
209
219
 
210
220
  ```bash
211
- python -m stata_code.kernel install --user
221
+ jupyter kernelspec list
222
+ # should include an entry named `stata`
212
223
  ```
213
224
 
214
- Then open a notebook and select the **Stata** kernel. Stata commands run in cells; logs, graphs, and warnings render inline.
225
+ Then open Jupyter Notebook / JupyterLab (or a `.ipynb` in VS Code), pick **Stata** in the kernel selector, and run Stata commands in cells. Logs, graphs, and warnings render inline.
226
+
227
+ > JupyterLab's Extension Manager only installs front-end JS extensions, so it cannot install a kernel — `pip install` plus the `install --user` step above is the only supported path.
215
228
 
216
229
  ### As a VS Code Extension
217
230
 
@@ -224,7 +237,7 @@ code --install-extension brycewang-stanford.stata-code-vscode
224
237
 
225
238
  Or open the **Extensions** sidebar in VS Code and search `stata-code`.
226
239
 
227
- The extension still requires `stata-code` itself to be importable on your system Python (`pip install stata-code`), so that `stata-code-mcp` resolves on `PATH`. Stata 17+ and a valid Stata license are required as for any other frontend.
240
+ The extension still requires the MCP extra on your system Python (`pip install "stata-code[mcp]"`), so that `stata-code-mcp` resolves on `PATH` and can import the MCP SDK. Stata 17+ and a valid Stata license are required as for any other frontend.
228
241
 
229
242
  ---
230
243
 
@@ -464,7 +477,7 @@ else:
464
477
 
465
478
  ### 作为 MCP server
466
479
 
467
- `pip install stata-code` 之后,`stata-code-mcp` 会出现在你的 `PATH` 中。可以接到 Claude Code、Cursor、Claude Desktop 等任何兼容 MCP 的客户端里。
480
+ `pip install "stata-code[mcp]"` 之后,`stata-code-mcp` 会出现在你的 `PATH` 中。可以接到 Claude Code、Cursor、Claude Desktop 等任何兼容 MCP 的客户端里。
468
481
 
469
482
  #### 用 `claude mcp add` 接入 Claude Code(推荐)
470
483
 
@@ -530,17 +543,30 @@ MCP server 注册了 8 个工具:
530
543
 
531
544
  ### 作为 Jupyter kernel
532
545
 
546
+ `stata-code` 的 Jupyter 支持是以 **kernel** 形式打包在 Python 包里的 —— JupyterLab 插件市场里**没有**独立的 "stata-code 插件"。安装分两步:先 `pip install` 安装带 `kernel` extra 的包,再把 kernelspec 注册到 Jupyter。
547
+
548
+ **前置条件**:本机已经安装 Stata 17+ 且持有合法许可证(kernel 通过 `pystata` 调用本地 Stata),同一个 Python 环境里已经装好 `jupyter`/`jupyterlab`,Python 版本 ≥ 3.10。
549
+
533
550
  ```bash
551
+ # 1. 安装带 kernel extra 的 stata-code(会同时装上 ipykernel)
552
+ pip install "stata-code[kernel]"
553
+
554
+ # 2. 把 kernelspec 注册到当前用户的 Jupyter data dir
534
555
  stata-code-kernel install --user
556
+ # 等价命令:
557
+ # python -m stata_code.kernel install --user
535
558
  ```
536
559
 
537
- 也可以直接以 module 方式安装:
560
+ 检查 kernel 是否注册成功:
538
561
 
539
562
  ```bash
540
- python -m stata_code.kernel install --user
563
+ jupyter kernelspec list
564
+ # 输出里应该能看到名为 `stata` 的条目
541
565
  ```
542
566
 
543
- 然后打开 notebook,选择 **Stata** kernel。Stata 命令会在 cell 中运行,日志、图形和 warnings 会以内联方式显示。
567
+ 然后打开 Jupyter Notebook / JupyterLab(或 VS Code 中的 `.ipynb`),在 kernel 选择器里挑 **Stata**,cell 里直接写 Stata 命令即可,日志、graphs warnings 会以内联方式显示。
568
+
569
+ > JupyterLab 的 Extension Manager 只能安装前端 JS 扩展,**装不了 kernel**。所以上面的 `pip install` + `install --user` 是唯一支持的安装路径。
544
570
 
545
571
  ### 作为 VS Code 扩展
546
572
 
@@ -553,7 +579,7 @@ code --install-extension brycewang-stanford.stata-code-vscode
553
579
 
554
580
  或者打开 VS Code 的 **Extensions** 侧栏,搜索 `stata-code`。
555
581
 
556
- 扩展仍然依赖系统 Python 上能导入 `stata-code`(`pip install stata-code`),从而保证 `stata-code-mcp` 在 `PATH` 上可用。和其它前端一样,需要 Stata 17+ 和有效的 Stata 许可证。
582
+ 扩展仍然依赖系统 Python 上安装了 MCP extra(`pip install "stata-code[mcp]"`),从而保证 `stata-code-mcp` 在 `PATH` 上可用,并且能导入 MCP SDK。和其它前端一样,需要 Stata 17+ 和有效的 Stata 许可证。
557
583
 
558
584
  ---
559
585
 
@@ -97,7 +97,7 @@ else:
97
97
 
98
98
  ### As an MCP Server
99
99
 
100
- After `pip install stata-code`, the `stata-code-mcp` binary is on your `PATH`. You can wire it into Claude Code, Cursor, Claude Desktop, or any other MCP-compatible client.
100
+ After `pip install "stata-code[mcp]"`, the `stata-code-mcp` binary is on your `PATH`. You can wire it into Claude Code, Cursor, Claude Desktop, or any other MCP-compatible client.
101
101
 
102
102
  #### Claude Code via `claude mcp add` (recommended)
103
103
 
@@ -163,17 +163,30 @@ The MCP server registers 8 tools:
163
163
 
164
164
  ### As a Jupyter Kernel
165
165
 
166
+ `stata-code` ships a Jupyter kernel as part of the Python package — there is no separate "Jupyter plugin" in the JupyterLab extension marketplace. Installation is two steps: `pip install` the package with the `kernel` extra, then register the kernelspec with Jupyter.
167
+
168
+ **Prerequisites**: Stata 17+ installed locally with a valid license (the kernel calls Stata via `pystata`), and Python 3.10+ with `jupyter`/`jupyterlab` already on the same environment.
169
+
166
170
  ```bash
171
+ # 1. Install stata-code with the kernel extra (pulls in ipykernel)
172
+ pip install "stata-code[kernel]"
173
+
174
+ # 2. Register the kernelspec into Jupyter's user data dir
167
175
  stata-code-kernel install --user
176
+ # Or, equivalently:
177
+ # python -m stata_code.kernel install --user
168
178
  ```
169
179
 
170
- Or install it as a module:
180
+ Verify the kernel is registered:
171
181
 
172
182
  ```bash
173
- python -m stata_code.kernel install --user
183
+ jupyter kernelspec list
184
+ # should include an entry named `stata`
174
185
  ```
175
186
 
176
- Then open a notebook and select the **Stata** kernel. Stata commands run in cells; logs, graphs, and warnings render inline.
187
+ Then open Jupyter Notebook / JupyterLab (or a `.ipynb` in VS Code), pick **Stata** in the kernel selector, and run Stata commands in cells. Logs, graphs, and warnings render inline.
188
+
189
+ > JupyterLab's Extension Manager only installs front-end JS extensions, so it cannot install a kernel — `pip install` plus the `install --user` step above is the only supported path.
177
190
 
178
191
  ### As a VS Code Extension
179
192
 
@@ -186,7 +199,7 @@ code --install-extension brycewang-stanford.stata-code-vscode
186
199
 
187
200
  Or open the **Extensions** sidebar in VS Code and search `stata-code`.
188
201
 
189
- The extension still requires `stata-code` itself to be importable on your system Python (`pip install stata-code`), so that `stata-code-mcp` resolves on `PATH`. Stata 17+ and a valid Stata license are required as for any other frontend.
202
+ The extension still requires the MCP extra on your system Python (`pip install "stata-code[mcp]"`), so that `stata-code-mcp` resolves on `PATH` and can import the MCP SDK. Stata 17+ and a valid Stata license are required as for any other frontend.
190
203
 
191
204
  ---
192
205
 
@@ -426,7 +439,7 @@ else:
426
439
 
427
440
  ### 作为 MCP server
428
441
 
429
- `pip install stata-code` 之后,`stata-code-mcp` 会出现在你的 `PATH` 中。可以接到 Claude Code、Cursor、Claude Desktop 等任何兼容 MCP 的客户端里。
442
+ `pip install "stata-code[mcp]"` 之后,`stata-code-mcp` 会出现在你的 `PATH` 中。可以接到 Claude Code、Cursor、Claude Desktop 等任何兼容 MCP 的客户端里。
430
443
 
431
444
  #### 用 `claude mcp add` 接入 Claude Code(推荐)
432
445
 
@@ -492,17 +505,30 @@ MCP server 注册了 8 个工具:
492
505
 
493
506
  ### 作为 Jupyter kernel
494
507
 
508
+ `stata-code` 的 Jupyter 支持是以 **kernel** 形式打包在 Python 包里的 —— JupyterLab 插件市场里**没有**独立的 "stata-code 插件"。安装分两步:先 `pip install` 安装带 `kernel` extra 的包,再把 kernelspec 注册到 Jupyter。
509
+
510
+ **前置条件**:本机已经安装 Stata 17+ 且持有合法许可证(kernel 通过 `pystata` 调用本地 Stata),同一个 Python 环境里已经装好 `jupyter`/`jupyterlab`,Python 版本 ≥ 3.10。
511
+
495
512
  ```bash
513
+ # 1. 安装带 kernel extra 的 stata-code(会同时装上 ipykernel)
514
+ pip install "stata-code[kernel]"
515
+
516
+ # 2. 把 kernelspec 注册到当前用户的 Jupyter data dir
496
517
  stata-code-kernel install --user
518
+ # 等价命令:
519
+ # python -m stata_code.kernel install --user
497
520
  ```
498
521
 
499
- 也可以直接以 module 方式安装:
522
+ 检查 kernel 是否注册成功:
500
523
 
501
524
  ```bash
502
- python -m stata_code.kernel install --user
525
+ jupyter kernelspec list
526
+ # 输出里应该能看到名为 `stata` 的条目
503
527
  ```
504
528
 
505
- 然后打开 notebook,选择 **Stata** kernel。Stata 命令会在 cell 中运行,日志、图形和 warnings 会以内联方式显示。
529
+ 然后打开 Jupyter Notebook / JupyterLab(或 VS Code 中的 `.ipynb`),在 kernel 选择器里挑 **Stata**,cell 里直接写 Stata 命令即可,日志、graphs warnings 会以内联方式显示。
530
+
531
+ > JupyterLab 的 Extension Manager 只能安装前端 JS 扩展,**装不了 kernel**。所以上面的 `pip install` + `install --user` 是唯一支持的安装路径。
506
532
 
507
533
  ### 作为 VS Code 扩展
508
534
 
@@ -515,7 +541,7 @@ code --install-extension brycewang-stanford.stata-code-vscode
515
541
 
516
542
  或者打开 VS Code 的 **Extensions** 侧栏,搜索 `stata-code`。
517
543
 
518
- 扩展仍然依赖系统 Python 上能导入 `stata-code`(`pip install stata-code`),从而保证 `stata-code-mcp` 在 `PATH` 上可用。和其它前端一样,需要 Stata 17+ 和有效的 Stata 许可证。
544
+ 扩展仍然依赖系统 Python 上安装了 MCP extra(`pip install "stata-code[mcp]"`),从而保证 `stata-code-mcp` 在 `PATH` 上可用,并且能导入 MCP SDK。和其它前端一样,需要 Stata 17+ 和有效的 Stata 许可证。
519
545
 
520
546
  ---
521
547
 
@@ -228,6 +228,7 @@ The single biggest token-economy decision in the schema. Default response carrie
228
228
  | `complete` | `bool` | Reserved for v2 streaming. Always `true` in v1. v2 may emit interim results with `complete: false`. |
229
229
  | `error_window` | `string \| null` | When `error` is non-null, the ~10 log lines immediately surrounding the failing emission (regardless of `head`/`tail` window). Cheap for the producer to compute; saves agents from bumping `log_lines` or fetching the full log just to see "what did Stata say right when it broke." `null` on success or when not computable. |
230
230
  | `ref` | `string \| null` | Opaque reference for `get_log`. Required when `truncated: true`; may be set when `truncated: false` for caller convenience; `null` is allowed when full log is in `head`. |
231
+ | `files` | `object \| null` | Persistent `.log` / `.smcl` artifacts written for file-backed runs when requested. `null` when no files were written. See "Persistent log files" below. |
231
232
 
232
233
  **ANSI handling.** All log views (`head`, `tail`, `error_window`, the payload returned by `get_log(ref)`) are ANSI-escape-stripped, consistently.
233
234
 
@@ -237,6 +238,37 @@ The single biggest token-economy decision in the schema. Default response carrie
237
238
 
238
239
  **Defaults.** `head=20`, `tail=20`. Configurable per call via `log_lines_head` / `log_lines_tail` (see §4). If `lines_total ≤ head+tail`, the producer MUST set `truncated: false`, place the full log in `head`, set `tail: ""`, and set `ref: null`.
239
240
 
241
+ **Persistent log files.** When a frontend passes a source `.do` path and requests `persist_log_files`, producers write immutable run artifacts under:
242
+
243
+ ```text
244
+ <do-file-dir>/log-files/<do-stem>__<UTC timestamp>__<session_id>__<request_id>/
245
+ ```
246
+
247
+ `log.files` then has:
248
+
249
+ ```json
250
+ {
251
+ "directory": "/abs/path/log-files/test1__20260508T012233123Z__main__abc123",
252
+ "log_path": "/abs/path/.../test1__20260508T012233123Z__main__abc123.log",
253
+ "smcl_path": "/abs/path/.../test1__20260508T012233123Z__main__abc123.smcl",
254
+ "manifest_path": "/abs/path/.../manifest.json",
255
+ "code_path": "/abs/path/.../submitted.do",
256
+ "working_dir": "/abs/path",
257
+ "graphs_dir": "/abs/path/.../graphs",
258
+ "outputs_dir": "/abs/path/.../outputs",
259
+ "graph_paths": ["/abs/path/.../graphs/01-Graph.png"],
260
+ "output_paths": ["/abs/path/.../outputs/table.xlsx"],
261
+ "policy": "per_run_directory",
262
+ "append": false
263
+ }
264
+ ```
265
+
266
+ The stable folder name is `log-files`; timestamps belong on child run directories, not on the root. Producers SHOULD NOT append different executions into one log file, because parallel sessions, reruns after a pause, and selection/cell executions become ambiguous. Each run directory SHOULD include a manifest and submitted-code snapshot so the log is attributable without relying on editor history.
267
+
268
+ When `origin_path` is supplied, producers SHOULD default Stata's working directory to the source `.do` file's directory before running. This mirrors how users organize project-relative `graph export`, `putexcel`, `esttab using`, `collect export`, and similar output commands. Frontends may disable this with `use_origin_workdir: false` or override it with `working_dir`.
269
+
270
+ When `persist_generated_files` is true, producers SHOULD copy newly created or modified common output files from the run working directory into `outputs/`, preserving relative paths where practical. Captured graph refs SHOULD also be materialized into `graphs/`, with the corresponding `GraphInfo.file_path` set.
271
+
240
272
  ### 3.4 `results`
241
273
 
242
274
  Stata's `r()` and `e()` return dictionaries, structurally separated. Each follows the same shape:
@@ -317,6 +349,7 @@ Each entry describes one captured graph. By default the bytes are **not** inline
317
349
  | `source_command` | `string \| null` | The user-submitted command line that produced this graph, when isolatable. |
318
350
  | `source_line` | `int \| null` | 1-indexed line within the submitted code that produced this graph. |
319
351
  | `inline` | `string \| null` | Base64-encoded bytes when the caller explicitly asked for inline (`include_graphs: "inline"`); else `null`. |
352
+ | `file_path` | `string \| null` | Persistent graph file path when the run bundle materialized captured graphs under `log.files.graphs_dir`; else `null`. |
320
353
 
321
354
  ### 3.7 `error`
322
355
 
@@ -426,6 +459,11 @@ The schema also dictates what callers may *ask for*. Every frontend exposes the
426
459
  | `graph_format` | `"png" \| "svg" \| "pdf"` | `"png"` | Render format. |
427
460
  | `include_dataset_variables` | `bool` | `true` | Set `false` to omit `dataset.variables`. |
428
461
  | `timeout_ms` | `int \| null` | `600000` (10 min) | Hard timeout. `null` disables. On expiry, returns `ok: false`, `error.kind: "timeout"`, `rc: -2`. Frontends MAY override the default if their use case demands. |
462
+ | `persist_log_files` | `bool` | `false` | With `origin_path`, writes immutable `.log` / `.smcl` / manifest files under the source `.do` file's `log-files/` directory. |
463
+ | `persist_generated_files` | `bool` | `true` | When log files are persisted, also copies newly created or modified table/export files into `outputs/` and captured graphs into `graphs/`. |
464
+ | `origin_path` | `string \| null` | `null` | Absolute source `.do` path used for working-directory defaults and run-bundle placement. |
465
+ | `use_origin_workdir` | `bool` | `true` | With `origin_path`, `cd` Stata to the source `.do` directory before running. |
466
+ | `working_dir` | `string \| null` | `null` | Explicit Stata working directory; overrides the source `.do` directory. |
429
467
 
430
468
  Frontends translate their native idiom (MCP `inputSchema`, Jupyter kernel options, VSCode commands) into these names without renaming.
431
469
 
@@ -481,6 +519,8 @@ These are *additions* to `run()`. A minimal client only needs `run()` plus which
481
519
  | `matrix_ref` | Producer can emit large matrices as refs and supports `get_matrix`. |
482
520
  | `multi_session` | Producer supports `session_id != "main"` and `list_sessions`. |
483
521
  | `inline_graphs` | Producer supports `include_graphs: "inline"`. |
522
+ | `log_files` | Producer can persist immutable per-run `.log` / `.smcl` bundles. |
523
+ | `run_artifacts` | Producer can materialize captured graphs and copied table/export outputs into the run bundle. |
484
524
 
485
525
  Consumers detect optional features via `capabilities`, not by parsing `schema_version`. Producers may add entries; agents MUST treat unknown capability names as opaque.
486
526
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "stata-code"
7
- version = "0.3.1"
7
+ version = "0.4.0"
8
8
  description = "Agent-native Stata bridge — one core, multiple frontends (MCP, Jupyter, VSCode)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -247,6 +247,18 @@
247
247
  "GraphInfo": {
248
248
  "additionalProperties": true,
249
249
  "properties": {
250
+ "file_path": {
251
+ "anyOf": [
252
+ {
253
+ "type": "string"
254
+ },
255
+ {
256
+ "type": "null"
257
+ }
258
+ ],
259
+ "default": null,
260
+ "title": "File Path"
261
+ },
250
262
  "format": {
251
263
  "$ref": "#/$defs/GraphFormat",
252
264
  "default": "png"
@@ -327,6 +339,109 @@
327
339
  "title": "GraphInfo",
328
340
  "type": "object"
329
341
  },
342
+ "LogFileInfo": {
343
+ "additionalProperties": true,
344
+ "description": "Persistent on-disk artifacts written for file-backed runs.",
345
+ "properties": {
346
+ "append": {
347
+ "default": false,
348
+ "title": "Append",
349
+ "type": "boolean"
350
+ },
351
+ "code_path": {
352
+ "anyOf": [
353
+ {
354
+ "type": "string"
355
+ },
356
+ {
357
+ "type": "null"
358
+ }
359
+ ],
360
+ "default": null,
361
+ "title": "Code Path"
362
+ },
363
+ "directory": {
364
+ "title": "Directory",
365
+ "type": "string"
366
+ },
367
+ "graph_paths": {
368
+ "items": {
369
+ "type": "string"
370
+ },
371
+ "title": "Graph Paths",
372
+ "type": "array"
373
+ },
374
+ "graphs_dir": {
375
+ "anyOf": [
376
+ {
377
+ "type": "string"
378
+ },
379
+ {
380
+ "type": "null"
381
+ }
382
+ ],
383
+ "default": null,
384
+ "title": "Graphs Dir"
385
+ },
386
+ "log_path": {
387
+ "title": "Log Path",
388
+ "type": "string"
389
+ },
390
+ "manifest_path": {
391
+ "title": "Manifest Path",
392
+ "type": "string"
393
+ },
394
+ "output_paths": {
395
+ "items": {
396
+ "type": "string"
397
+ },
398
+ "title": "Output Paths",
399
+ "type": "array"
400
+ },
401
+ "outputs_dir": {
402
+ "anyOf": [
403
+ {
404
+ "type": "string"
405
+ },
406
+ {
407
+ "type": "null"
408
+ }
409
+ ],
410
+ "default": null,
411
+ "title": "Outputs Dir"
412
+ },
413
+ "policy": {
414
+ "const": "per_run_directory",
415
+ "default": "per_run_directory",
416
+ "title": "Policy",
417
+ "type": "string"
418
+ },
419
+ "smcl_path": {
420
+ "title": "Smcl Path",
421
+ "type": "string"
422
+ },
423
+ "working_dir": {
424
+ "anyOf": [
425
+ {
426
+ "type": "string"
427
+ },
428
+ {
429
+ "type": "null"
430
+ }
431
+ ],
432
+ "default": null,
433
+ "title": "Working Dir"
434
+ }
435
+ },
436
+ "required": [
437
+ "directory",
438
+ "log_path",
439
+ "smcl_path",
440
+ "manifest_path"
441
+ ],
442
+ "title": "LogFileInfo",
443
+ "type": "object"
444
+ },
330
445
  "LogInfo": {
331
446
  "additionalProperties": true,
332
447
  "properties": {
@@ -352,6 +467,17 @@
352
467
  "default": null,
353
468
  "title": "Error Window"
354
469
  },
470
+ "files": {
471
+ "anyOf": [
472
+ {
473
+ "$ref": "#/$defs/LogFileInfo"
474
+ },
475
+ {
476
+ "type": "null"
477
+ }
478
+ ],
479
+ "default": null
480
+ },
355
481
  "head": {
356
482
  "default": "",
357
483
  "title": "Head",
@@ -42,6 +42,7 @@ from stata_code.core.schema import (
42
42
  GraphFormat,
43
43
  GraphInfo,
44
44
  IncludeGraphs,
45
+ LogFileInfo,
45
46
  LogInfo,
46
47
  Matrix,
47
48
  ResultsInfo,
@@ -57,7 +58,7 @@ from stata_code.core.schema import (
57
58
  # Convenience alias: `run(...)` == `execute(...)`.
58
59
  run = execute
59
60
 
60
- __version__ = "0.3.1"
61
+ __version__ = "0.4.0"
61
62
 
62
63
  __all__ = [
63
64
  # Primary entry points
@@ -86,6 +87,7 @@ __all__ = [
86
87
  "GraphInfo",
87
88
  "IncludeGraphs",
88
89
  "LogInfo",
90
+ "LogFileInfo",
89
91
  "Matrix",
90
92
  "ResultsInfo",
91
93
  "StataEdition",
@@ -27,6 +27,7 @@ from stata_code.core.schema import (
27
27
  GraphFormat,
28
28
  GraphInfo,
29
29
  IncludeGraphs,
30
+ LogFileInfo,
30
31
  LogInfo,
31
32
  Matrix,
32
33
  ResultsInfo,
@@ -58,6 +59,7 @@ __all__ = [
58
59
  "ErrorContext",
59
60
  "Suggestion",
60
61
  "LogInfo",
62
+ "LogFileInfo",
61
63
  "ResultsInfo",
62
64
  "StataReturns",
63
65
  "Matrix",