stata-code 0.6.4__tar.gz → 0.7.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 (58) hide show
  1. {stata_code-0.6.4 → stata_code-0.7.0}/CHANGELOG.md +50 -0
  2. {stata_code-0.6.4 → stata_code-0.7.0}/PKG-INFO +18 -5
  3. {stata_code-0.6.4 → stata_code-0.7.0}/README.md +17 -4
  4. {stata_code-0.6.4 → stata_code-0.7.0}/SCHEMA.md +10 -7
  5. {stata_code-0.6.4 → stata_code-0.7.0}/pyproject.toml +1 -1
  6. {stata_code-0.6.4 → stata_code-0.7.0}/scripts/check_versions.py +16 -1
  7. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/__init__.py +1 -1
  8. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/_pool.py +12 -0
  9. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/log_artifacts.py +49 -5
  10. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/notebook.py +199 -15
  11. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/run_index.py +68 -9
  12. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/runner.py +85 -13
  13. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/mcp/server.py +28 -29
  14. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_log_artifacts.py +50 -0
  15. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_mcp.py +66 -15
  16. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_notebook.py +20 -0
  17. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_notebook_phase2.py +428 -0
  18. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_pool.py +54 -0
  19. stata_code-0.7.0/tests/test_release_versions.py +109 -0
  20. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_run_index.py +49 -0
  21. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_runner.py +28 -6
  22. {stata_code-0.6.4 → stata_code-0.7.0}/.gitignore +0 -0
  23. {stata_code-0.6.4 → stata_code-0.7.0}/LICENSE +0 -0
  24. {stata_code-0.6.4 → stata_code-0.7.0}/LICENSE-POLICY.md +0 -0
  25. {stata_code-0.6.4 → stata_code-0.7.0}/PUBLISHING.md +0 -0
  26. {stata_code-0.6.4 → stata_code-0.7.0}/docs/design/hard_timeout.md +0 -0
  27. {stata_code-0.6.4 → stata_code-0.7.0}/examples/01-basic-regression.md +0 -0
  28. {stata_code-0.6.4 → stata_code-0.7.0}/examples/02-did-card-krueger.md +0 -0
  29. {stata_code-0.6.4 → stata_code-0.7.0}/examples/03-graphs.md +0 -0
  30. {stata_code-0.6.4 → stata_code-0.7.0}/examples/04-multi-session.md +0 -0
  31. {stata_code-0.6.4 → stata_code-0.7.0}/examples/05-large-matrix.md +0 -0
  32. {stata_code-0.6.4 → stata_code-0.7.0}/examples/README.md +0 -0
  33. {stata_code-0.6.4 → stata_code-0.7.0}/schema/run_result.schema.json +0 -0
  34. {stata_code-0.6.4 → stata_code-0.7.0}/scripts/export_schema.py +0 -0
  35. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/__init__.py +0 -0
  36. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/_refs.py +0 -0
  37. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/_runtime.py +0 -0
  38. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/errors.py +0 -0
  39. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/schema.py +0 -0
  40. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/kernel/__init__.py +0 -0
  41. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/kernel/__main__.py +0 -0
  42. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/kernel/assets/logo-32x32.png +0 -0
  43. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/kernel/assets/logo-64x64.png +0 -0
  44. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/kernel/assets/logo-svg.svg +0 -0
  45. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/kernel/kernel.py +0 -0
  46. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/mcp/__init__.py +0 -0
  47. {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/mcp/__main__.py +0 -0
  48. {stata_code-0.6.4 → stata_code-0.7.0}/tests/__init__.py +0 -0
  49. {stata_code-0.6.4 → stata_code-0.7.0}/tests/conftest.py +0 -0
  50. {stata_code-0.6.4 → stata_code-0.7.0}/tests/fixtures/.gitkeep +0 -0
  51. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_cancel.py +0 -0
  52. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_errors.py +0 -0
  53. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_kernel.py +0 -0
  54. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_mcp_stdio.py +0 -0
  55. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_public_api.py +0 -0
  56. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_runtime_discovery.py +0 -0
  57. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_schema.py +0 -0
  58. {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_schema_artifact.py +0 -0
@@ -6,6 +6,56 @@ to semver-major.minor for the result schema (see `SCHEMA.md` §6).
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## 0.7.0 — 2026-05-30
10
+
11
+ ### Added
12
+
13
+ - **Schema-compatible public session ids end to end.** Runner, pool, MCP,
14
+ and VS Code now accept the schema's `[A-Za-z0-9_-]+` session ids. Values
15
+ that Stata cannot use as frame names, such as `model-a` or `9abc`, are
16
+ mapped to deterministic private frame names internally while results and
17
+ session listings keep echoing the public id.
18
+ - **Graph source attribution.** Captured graphs now receive best-effort
19
+ `source_command` / `source_line` metadata from the submitted code for
20
+ named graphs and unambiguous unnamed graph creation.
21
+ - **VS Code pure formatter/session helpers.** Log/data-preview/matrix
22
+ formatting and session-id rules moved out of `extension.ts` into small
23
+ tested modules, reducing the monolithic extension entrypoint.
24
+ - **Run-index pagination.** `list_runs` now accepts `offset` alongside
25
+ `limit` so agents can page through long run-bundle histories.
26
+
27
+ ### Fixed
28
+
29
+ - **Release-version drift guard.** `scripts/check_versions.py` now checks
30
+ `vscode/package-lock.json` plus the Claude plugin marketplace manifests,
31
+ and has regression tests for those release surfaces.
32
+ - **Pool invalid-request classification.** Worker-side `ValueError` and
33
+ `NotImplementedError` now propagate as caller errors instead of being
34
+ wrapped as `adapter_crash`.
35
+ - **VS Code RunResult type drift.** The hand-written TypeScript type now
36
+ includes `origin` and nullable schema fields such as `stata.version` and
37
+ `stata_elapsed_ms`.
38
+ - **Run-index `since` filtering.** Date-only and seconds-only `since`
39
+ values are normalized to canonical millisecond UTC before comparison, and
40
+ malformed values now raise a typed `since_invalid` error.
41
+ - **Run-bundle manifest writes.** Manifest creation and post-run artifact
42
+ rewrites now use temp-file-and-rename writes with fsync so concurrent
43
+ `list_runs` readers do not observe torn JSON.
44
+ - **Notebook repair hardening.** Cell edits now retain a compact summary of
45
+ outputs they clear, abort if the notebook is deleted between read and write,
46
+ repair fully-id'd pre-4.5 metadata, and expose malformed raw cell indices in
47
+ `notebook_outline`.
48
+
49
+ ## 0.6.5 — 2026-05-22
50
+
51
+ ### Fixed
52
+
53
+ - **OpenAI tool-schema compatibility.** `notebook_locate` and
54
+ `notebook_insert_cell` no longer advertise top-level `oneOf` constraints in
55
+ their MCP input schemas. OpenAI rejects those schemas during tool
56
+ registration, while the server-side runtime guards still enforce the
57
+ "exactly one query/anchor" rules.
58
+
9
59
  ## 0.6.4 — 2026-05-21
10
60
 
11
61
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stata-code
3
- Version: 0.6.4
3
+ Version: 0.7.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
@@ -263,6 +263,19 @@ When `stata-code-mcp` lives inside a project virtualenv (recommended for reprodu
263
263
 
264
264
  For `uvx`-only setups, set `"command": "uvx"` and `"args": ["--from", "stata-code", "stata-code-mcp"]`.
265
265
 
266
+ #### MCP troubleshooting
267
+
268
+ If `stata_run` reports `adapter_crash` with `worker emitted non-JSON: '\n'`,
269
+ upgrade to `stata-code>=0.6.4`, then restart the MCP client so it launches a
270
+ fresh server process. Also check that the client is resolving the expected
271
+ `stata-code-mcp` binary; project virtualenv installs should use the absolute
272
+ `.venv/bin/stata-code-mcp` path instead of relying on a global `PATH` entry.
273
+
274
+ If an OpenAI-backed client reports `API Error: 400 Invalid schema for function
275
+ 'mcp__stata-code__notebook_insert_cell'` and mentions a top-level `oneOf`,
276
+ upgrade to `stata-code>=0.6.5`, then restart the MCP client. Older server
277
+ processes keep advertising the stale schema until they are restarted.
278
+
266
279
  The MCP server registers 15 tools:
267
280
 
268
281
  | Tool | Purpose |
@@ -281,7 +294,7 @@ The MCP server registers 15 tools:
281
294
  | `notebook_edit_cell` | Atomically replace one cell's source (preserves id, clears outputs) |
282
295
  | `notebook_insert_cell` | Insert a new cell with a fresh nbformat 4.5+ UUID |
283
296
  | `notebook_delete_cell` | Remove a cell by id |
284
- | `list_runs` | Query run-bundle manifests (filter by notebook / cell_id / session / since / ok) |
297
+ | `list_runs` | Query run-bundle manifests (filter by notebook / cell_id / session / since / ok, page with limit / offset) |
285
298
 
286
299
  For modern MCP clients, these tools now return structured results through
287
300
  `structuredContent` with `outputSchema` metadata, while still keeping the
@@ -435,9 +448,9 @@ stata_code/
435
448
 
436
449
  - v1.0 result schema ([SCHEMA.md](SCHEMA.md))
437
450
  - `pystata`-based runner with native-typed `r()`, `e()`, and matrices
438
- - Multi-session via Stata frames
451
+ - 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)
439
452
  - Per-line error attribution: line number, context, commands_executed
440
- - Graph capture: `png` / `svg` / `pdf` with ref store
453
+ - Graph capture: `png` / `svg` / `pdf` with ref store and source-command attribution
441
454
  - Log truncation with ref store
442
455
  - Warning extraction: 5 categories + generic notes
443
456
  - 32-kind error taxonomy with canonical suggestions
@@ -446,7 +459,7 @@ stata_code/
446
459
  - Matrix size cap + `get_matrix(ref)` for large matrices (>10k cells)
447
460
  - Subprocess-backed hard timeout and cancellation for the public Python API and MCP server: `timeout_ms`, `cancel(session_id)`, and MCP `cancel_session`
448
461
  - 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`
449
- - Persistent run bundles + `list_runs` query over `manifest.json` files (filter by cell / origin / session / since / ok)
462
+ - Persistent run bundles + `list_runs` query over `manifest.json` files (filter by cell / origin / session / since / ok; page with limit / offset)
450
463
  - JSON Schema artifact auto-generated from `schema.py`: [`schema/run_result.schema.json`](schema/run_result.schema.json)
451
464
  - VS Code extension published to the Marketplace as [`brycewang-stanford.stata-code-vscode`](https://marketplace.visualstudio.com/items?itemName=brycewang-stanford.stata-code-vscode): syntax highlighting, section outline/navigation, code-lens cell and section runners, sidebar (sessions / last result / run history / logs / graphs), status bar, completions, conservative variable rename, diagnostics, MCP child-process spawn
452
465
  - Clean-room license policy ([LICENSE-POLICY.md](LICENSE-POLICY.md))
@@ -224,6 +224,19 @@ When `stata-code-mcp` lives inside a project virtualenv (recommended for reprodu
224
224
 
225
225
  For `uvx`-only setups, set `"command": "uvx"` and `"args": ["--from", "stata-code", "stata-code-mcp"]`.
226
226
 
227
+ #### MCP troubleshooting
228
+
229
+ If `stata_run` reports `adapter_crash` with `worker emitted non-JSON: '\n'`,
230
+ upgrade to `stata-code>=0.6.4`, then restart the MCP client so it launches a
231
+ fresh server process. Also check that the client is resolving the expected
232
+ `stata-code-mcp` binary; project virtualenv installs should use the absolute
233
+ `.venv/bin/stata-code-mcp` path instead of relying on a global `PATH` entry.
234
+
235
+ If an OpenAI-backed client reports `API Error: 400 Invalid schema for function
236
+ 'mcp__stata-code__notebook_insert_cell'` and mentions a top-level `oneOf`,
237
+ upgrade to `stata-code>=0.6.5`, then restart the MCP client. Older server
238
+ processes keep advertising the stale schema until they are restarted.
239
+
227
240
  The MCP server registers 15 tools:
228
241
 
229
242
  | Tool | Purpose |
@@ -242,7 +255,7 @@ The MCP server registers 15 tools:
242
255
  | `notebook_edit_cell` | Atomically replace one cell's source (preserves id, clears outputs) |
243
256
  | `notebook_insert_cell` | Insert a new cell with a fresh nbformat 4.5+ UUID |
244
257
  | `notebook_delete_cell` | Remove a cell by id |
245
- | `list_runs` | Query run-bundle manifests (filter by notebook / cell_id / session / since / ok) |
258
+ | `list_runs` | Query run-bundle manifests (filter by notebook / cell_id / session / since / ok, page with limit / offset) |
246
259
 
247
260
  For modern MCP clients, these tools now return structured results through
248
261
  `structuredContent` with `outputSchema` metadata, while still keeping the
@@ -396,9 +409,9 @@ stata_code/
396
409
 
397
410
  - v1.0 result schema ([SCHEMA.md](SCHEMA.md))
398
411
  - `pystata`-based runner with native-typed `r()`, `e()`, and matrices
399
- - Multi-session via Stata frames
412
+ - 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)
400
413
  - Per-line error attribution: line number, context, commands_executed
401
- - Graph capture: `png` / `svg` / `pdf` with ref store
414
+ - Graph capture: `png` / `svg` / `pdf` with ref store and source-command attribution
402
415
  - Log truncation with ref store
403
416
  - Warning extraction: 5 categories + generic notes
404
417
  - 32-kind error taxonomy with canonical suggestions
@@ -407,7 +420,7 @@ stata_code/
407
420
  - Matrix size cap + `get_matrix(ref)` for large matrices (>10k cells)
408
421
  - Subprocess-backed hard timeout and cancellation for the public Python API and MCP server: `timeout_ms`, `cancel(session_id)`, and MCP `cancel_session`
409
422
  - 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`
410
- - Persistent run bundles + `list_runs` query over `manifest.json` files (filter by cell / origin / session / since / ok)
423
+ - Persistent run bundles + `list_runs` query over `manifest.json` files (filter by cell / origin / session / since / ok; page with limit / offset)
411
424
  - JSON Schema artifact auto-generated from `schema.py`: [`schema/run_result.schema.json`](schema/run_result.schema.json)
412
425
  - VS Code extension published to the Marketplace as [`brycewang-stanford.stata-code-vscode`](https://marketplace.visualstudio.com/items?itemName=brycewang-stanford.stata-code-vscode): syntax highlighting, section outline/navigation, code-lens cell and section runners, sidebar (sessions / last result / run history / logs / graphs), status bar, completions, conservative variable rename, diagnostics, MCP child-process spawn
413
426
  - Clean-room license policy ([LICENSE-POLICY.md](LICENSE-POLICY.md))
@@ -185,7 +185,7 @@ A failed execution sets `ok: false`, `rc != 0`, and populates `error`:
185
185
  | --- | --- | --- | --- |
186
186
  | `ok` | `bool` | yes | The authoritative success bit. Producers MUST keep `ok`, `rc`, and `error`-presence consistent. Consumers branch on `ok` first. |
187
187
  | `rc` | `int` | yes | Stata's `_rc` after the last user-submitted command (after any `capture` masking). `0` on success. Synthetic codes are negative: `-1` adapter crash, `-2` timeout, `-3` cancellation. |
188
- | `session_id` | `string` | yes | Defaults to `"main"`. MUST match `[A-Za-z0-9_:-]+`. The character `:` is reserved for future remote-prefixing (e.g., `host-7:main`), so v1 producers MUST NOT emit colons. |
188
+ | `session_id` | `string` | yes | Defaults to `"main"`. MUST match `[A-Za-z0-9_-]+`. The character `:` is reserved for future remote-prefixing (e.g., `host-7:main`), so v1 producers MUST NOT emit colons. Producers may map ids that are not legal Stata frame names (for example `model-a` or `9abc`) to private frame names internally, but MUST echo the caller's `session_id` in the result. |
189
189
  | `request_id` | `string` | yes | Producer-generated, unique per call. Recommended format: ULID or UUIDv7 (sortable). Consumers use this for log correlation and `ref` lookup. |
190
190
  | `started_at` | `string` (ISO 8601 UTC) | yes | Timestamp at which the producer began handling the call, e.g. `"2026-04-30T14:22:08.123Z"`. Always UTC, always with millisecond precision. |
191
191
  | `elapsed_ms` | `int` | yes | Wall-clock duration of the call, in milliseconds. Minimum reported value is `1`; sub-millisecond calls round up. |
@@ -326,7 +326,7 @@ A summary of the active Stata frame *after* the command ran. Always populated.
326
326
 
327
327
  | Field | Type | Notes |
328
328
  | --- | --- | --- |
329
- | `frame` | `string` | Active frame name. Stata's master frame is named `"default"`. ⚠ Note this is unrelated to `session_id == "main"`. |
329
+ | `frame` | `string` | Active Stata frame name. Stata's master frame is named `"default"`. For session ids that are not legal Stata frame names, this may be a private generated frame name. ⚠ Note this is unrelated to `session_id == "main"`. |
330
330
  | `n_obs` | `int` | `_N`. |
331
331
  | `n_vars` | `int` | `c(k)`. |
332
332
  | `changed` | `bool` | `c(changed)`. ⚠ Stata sets this on *any* dataset-touching command, including no-op replaces — treat as a "may be dirty" hint, not a guarantee. |
@@ -481,7 +481,7 @@ The schema also dictates what callers may *ask for*. Every frontend exposes the
481
481
  | Option | Type | Default | Effect |
482
482
  | --- | --- | --- | --- |
483
483
  | `code` | `string` | — | The Stata code to run. |
484
- | `session_id` | `string` | `"main"` | Routes to a named persistent session. Pattern: `[A-Za-z0-9_-]+` (no colons in v1). |
484
+ | `session_id` | `string` | `"main"` | Routes to a named persistent session. Pattern: `[A-Za-z0-9_-]+` (no colons in v1). The public id is stable even when the backend maps it to a private Stata frame name. |
485
485
  | `log_lines_head` | `int` | `20` | Lines to retain at the start of `log.head`. `0` disables. |
486
486
  | `log_lines_tail` | `int` | `20` | Lines to retain at the end of `log.tail`. `0` disables. |
487
487
  | `include_full_log` | `bool` | `false` | If `true`, the full log is placed inline in `log.head` regardless of size; `truncated: false`, `ref: null`. Use when token budget is generous and follow-up calls are expensive. |
@@ -514,7 +514,7 @@ The schema implies a small set of follow-up calls. Frontends expose them under c
514
514
  | `list_sessions()` | Enumerate live sessions. | `[{session_id, started_at, last_used_at, n_obs}, ...]` |
515
515
  | `reset_session(session_id?)` | Hard-reset a session (`clear all`). Invalidates all refs scoped to it. | `Result` with the cleared state. |
516
516
  | `stata_info()` | Report installed Stata. | `{stata: {...}, available: bool, capabilities: [...]}` |
517
- | `list_runs(log_dir or origin_path, cell_id?, session_id?, ok?, since?, limit?)` | Read-only query over persisted run-bundle manifests. Returns newest-first compact summaries of prior runs that landed under `<origin dir>/log-files/`. | `{log_dir, scanned_count, match_count, skipped_count, limit, truncated, runs: [...]}` |
517
+ | `list_runs(log_dir or origin_path, cell_id?, session_id?, ok?, since?, limit?, offset?)` | Read-only query over persisted run-bundle manifests. Returns newest-first compact summaries of prior runs that landed under `<origin dir>/log-files/`. `since` accepts canonical millisecond UTC plus common date/seconds shorthands; `offset` pages through matches. | `{log_dir, scanned_count, match_count, skipped_count, limit, offset, truncated, runs: [...]}` |
518
518
 
519
519
  These are *additions* to `run()`. A minimal client only needs `run()` plus whichever auxiliaries match the truncation/ref behavior the producer can emit.
520
520
 
@@ -610,8 +610,9 @@ This section tracks how much of the schema is wired up in code. Not normative
610
610
  - `dataset` block — `n_obs`, `n_vars`, `frame`, `changed`, `filename`,
611
611
  and `variables` (capped at 200 entries).
612
612
  - `graphs[]` with `ref` + on-disk capture pipeline; format restricted to
613
- `png` / `svg` / `pdf`; PNG `width` / `height` parsed from IHDR.
614
- `inline` populated when `include_graphs="inline"`.
613
+ `png` / `svg` / `pdf`; PNG `width` / `height` parsed from IHDR;
614
+ best-effort `source_command` / `source_line` attribution from the
615
+ submitted code. `inline` populated when `include_graphs="inline"`.
615
616
  - Structured `error` — 32-kind enum, `varname` / `path` / `name`
616
617
  extracted from Stata's English error text by regex, structured
617
618
  `context` (`{before, failing, after}`), `commands_executed` parsed
@@ -620,7 +621,9 @@ This section tracks how much of the schema is wired up in code. Not normative
620
621
  - `request_id` (uuid4 hex), `started_at` (ISO 8601 UTC ms),
621
622
  `stata_elapsed_ms`, `capabilities`.
622
623
  - Multi-session via Stata frames — `session_id="main"` ↔ `default`
623
- frame; other ids create / route to same-named frames.
624
+ frame; other ids create / route to same-named frames when Stata allows
625
+ it, or deterministic private frame names when the public id needs
626
+ mapping.
624
627
  - `Warning` is `{kind, message}`; five built-in patterns
625
628
  (`omitted_collinear`, `convergence`, `singular`, `boundary`, generic
626
629
  `note`) + dedup.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "stata-code"
7
- version = "0.6.4"
7
+ version = "0.7.0"
8
8
  description = "Agent-native Stata bridge — one core, multiple frontends (MCP, Jupyter, VSCode)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -32,7 +32,7 @@ def _tag_version(tag: str) -> str:
32
32
 
33
33
  def main() -> int:
34
34
  parser = argparse.ArgumentParser(
35
- description="Check pyproject/Python/MCP/VS Code version literals."
35
+ description="Check Python, MCP, VS Code, and plugin version literals."
36
36
  )
37
37
  parser.add_argument(
38
38
  "--tag",
@@ -40,6 +40,10 @@ def main() -> int:
40
40
  )
41
41
  args = parser.parse_args()
42
42
 
43
+ vscode_lock = json.loads(_read("vscode/package-lock.json"))
44
+ plugin_manifest = json.loads(_read(".claude-plugin/plugin.json"))
45
+ marketplace_manifest = json.loads(_read(".claude-plugin/marketplace.json"))
46
+
43
47
  versions = {
44
48
  "pyproject.toml": _extract(
45
49
  "pyproject.toml", r'(?m)^\s*version\s*=\s*"([^"]+)"'
@@ -51,7 +55,18 @@ def main() -> int:
51
55
  "stata_code/mcp/server.py", r'(?m)^__version__\s*=\s*"([^"]+)"'
52
56
  ),
53
57
  "vscode/package.json": json.loads(_read("vscode/package.json"))["version"],
58
+ "vscode/package-lock.json": vscode_lock["version"],
59
+ "vscode/package-lock.json packages['']": vscode_lock["packages"][""]["version"],
60
+ ".claude-plugin/plugin.json": plugin_manifest["version"],
61
+ ".claude-plugin/marketplace.json metadata": marketplace_manifest["metadata"][
62
+ "version"
63
+ ],
54
64
  }
65
+ for idx, plugin in enumerate(marketplace_manifest.get("plugins", [])):
66
+ if isinstance(plugin, dict) and "version" in plugin:
67
+ versions[f".claude-plugin/marketplace.json plugins[{idx}]"] = plugin[
68
+ "version"
69
+ ]
55
70
 
56
71
  # mcpClient.ts used to carry a literal version; we now resolve it at
57
72
  # build time via ``import { version } from "../package.json"``. If the
@@ -174,7 +174,7 @@ def is_available() -> bool:
174
174
  return True
175
175
 
176
176
 
177
- __version__ = "0.6.4"
177
+ __version__ = "0.7.0"
178
178
 
179
179
  __all__ = [
180
180
  # Primary entry points
@@ -235,11 +235,19 @@ def _worker_main() -> int:
235
235
  }
236
236
  else:
237
237
  response = {"id": req_id, "ok": False, "error": f"unknown op: {op}"}
238
+ except (ValueError, NotImplementedError) as exc:
239
+ response = {
240
+ "id": req_id,
241
+ "ok": False,
242
+ "error": f"{type(exc).__name__}: {exc}",
243
+ "error_kind": "invalid_request",
244
+ }
238
245
  except Exception as exc: # noqa: BLE001
239
246
  response = {
240
247
  "id": req_id,
241
248
  "ok": False,
242
249
  "error": f"{type(exc).__name__}: {exc}",
250
+ "error_kind": "worker_error",
243
251
  }
244
252
  proto_out.write(json.dumps(response) + "\n")
245
253
  proto_out.flush()
@@ -354,6 +362,8 @@ class WorkerProcess:
354
362
  f"worker response id mismatch: expected {req_id}, got {response.get('id')}"
355
363
  )
356
364
  if not response.get("ok"):
365
+ if response.get("error_kind") == "invalid_request":
366
+ raise ValueError(response.get("error", "<no error>"))
357
367
  raise _WorkerError(
358
368
  f"worker reported failure: {response.get('error', '<no error>')}"
359
369
  )
@@ -478,6 +488,8 @@ class WorkerProcess:
478
488
  f"worker response id mismatch: expected {req_id}, got {response.get('id')}"
479
489
  )
480
490
  if not response.get("ok"):
491
+ if response.get("error_kind") == "invalid_request":
492
+ raise ValueError(response.get("error", "<no error>"))
481
493
  raise _WorkerError(
482
494
  f"worker reported failure: {response.get('error', '<no error>')}"
483
495
  )
@@ -6,6 +6,7 @@ import json
6
6
  import os
7
7
  import re
8
8
  import shutil
9
+ import tempfile
9
10
  import uuid
10
11
  from datetime import datetime, timezone
11
12
  from pathlib import Path
@@ -13,6 +14,50 @@ from typing import Any
13
14
 
14
15
  from stata_code.core.schema import LogFileInfo, StataInfo
15
16
 
17
+
18
+ def _atomic_write_text(path: Path, text: str) -> None:
19
+ """Write ``text`` to ``path`` atomically (temp file + rename in same dir).
20
+
21
+ The bytes are flushed and ``fsync``'d before the rename so a reader — the
22
+ run index scans ``manifest.json`` concurrently with new runs — never
23
+ observes a torn or half-written file. ``update_run_artifact_manifest`` also
24
+ rewrites the manifest in place after graphs/outputs are copied; doing that
25
+ non-atomically risked a partial JSON that ``list_runs`` would skip.
26
+ """
27
+ fd, tmp_name = tempfile.mkstemp(
28
+ prefix=f".{path.name}.", suffix=".tmp", dir=str(path.parent)
29
+ )
30
+ try:
31
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
32
+ f.write(text)
33
+ f.flush()
34
+ os.fsync(f.fileno())
35
+ os.replace(tmp_name, path)
36
+ _fsync_directory(path.parent)
37
+ except Exception:
38
+ try:
39
+ os.unlink(tmp_name)
40
+ except OSError:
41
+ pass
42
+ raise
43
+
44
+
45
+ def _fsync_directory(directory: Path) -> None:
46
+ """Best-effort durability for the rename entry itself."""
47
+ flags = os.O_RDONLY
48
+ if hasattr(os, "O_DIRECTORY"):
49
+ flags |= os.O_DIRECTORY
50
+ try:
51
+ fd = os.open(directory, flags)
52
+ except OSError:
53
+ return
54
+ try:
55
+ os.fsync(fd)
56
+ except OSError:
57
+ pass
58
+ finally:
59
+ os.close(fd)
60
+
16
61
  _SAFE_PART_RE = re.compile(r"[^A-Za-z0-9._-]+")
17
62
  _MAX_OUTPUT_ARTIFACT_BYTES = 50 * 1024 * 1024
18
63
  _OUTPUT_EXTENSIONS = {
@@ -128,7 +173,8 @@ def persist_run_log_files(
128
173
  policy="per_run_directory",
129
174
  append=False,
130
175
  )
131
- manifest_path.write_text(
176
+ _atomic_write_text(
177
+ manifest_path,
132
178
  json.dumps(
133
179
  _manifest(
134
180
  info=info,
@@ -151,7 +197,6 @@ def persist_run_log_files(
151
197
  sort_keys=True,
152
198
  )
153
199
  + "\n",
154
- encoding="utf-8",
155
200
  )
156
201
  return info
157
202
 
@@ -280,9 +325,9 @@ def update_run_artifact_manifest(info: LogFileInfo) -> None:
280
325
  }
281
326
  )
282
327
  manifest["working_dir"] = info.working_dir
283
- manifest_path.write_text(
328
+ _atomic_write_text(
329
+ manifest_path,
284
330
  json.dumps(manifest, indent=2, sort_keys=True) + "\n",
285
- encoding="utf-8",
286
331
  )
287
332
 
288
333
 
@@ -453,4 +498,3 @@ def _unique_file(path: Path) -> Path:
453
498
  raise FileExistsError(
454
499
  f"could not allocate unique artifact path under {path.parent}"
455
500
  )
456
-