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.
- {stata_code-0.6.4 → stata_code-0.7.0}/CHANGELOG.md +50 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/PKG-INFO +18 -5
- {stata_code-0.6.4 → stata_code-0.7.0}/README.md +17 -4
- {stata_code-0.6.4 → stata_code-0.7.0}/SCHEMA.md +10 -7
- {stata_code-0.6.4 → stata_code-0.7.0}/pyproject.toml +1 -1
- {stata_code-0.6.4 → stata_code-0.7.0}/scripts/check_versions.py +16 -1
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/__init__.py +1 -1
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/_pool.py +12 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/log_artifacts.py +49 -5
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/notebook.py +199 -15
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/run_index.py +68 -9
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/runner.py +85 -13
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/mcp/server.py +28 -29
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_log_artifacts.py +50 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_mcp.py +66 -15
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_notebook.py +20 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_notebook_phase2.py +428 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_pool.py +54 -0
- stata_code-0.7.0/tests/test_release_versions.py +109 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_run_index.py +49 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_runner.py +28 -6
- {stata_code-0.6.4 → stata_code-0.7.0}/.gitignore +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/LICENSE +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/LICENSE-POLICY.md +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/PUBLISHING.md +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/docs/design/hard_timeout.md +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/examples/01-basic-regression.md +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/examples/02-did-card-krueger.md +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/examples/03-graphs.md +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/examples/04-multi-session.md +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/examples/05-large-matrix.md +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/examples/README.md +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/schema/run_result.schema.json +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/scripts/export_schema.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/__init__.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/_refs.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/_runtime.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/errors.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/core/schema.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/kernel/__init__.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/kernel/__main__.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/kernel/assets/logo-32x32.png +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/kernel/assets/logo-64x64.png +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/kernel/assets/logo-svg.svg +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/kernel/kernel.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/mcp/__init__.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/stata_code/mcp/__main__.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/__init__.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/conftest.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/fixtures/.gitkeep +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_cancel.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_errors.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_kernel.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_mcp_stdio.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_public_api.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_runtime_discovery.py +0 -0
- {stata_code-0.6.4 → stata_code-0.7.0}/tests/test_schema.py +0 -0
- {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.
|
|
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_
|
|
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
|
-
`
|
|
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.
|
|
@@ -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
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|