bathos 0.3.0__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.
- {bathos-0.3.0 → bathos-0.4.0}/CHANGELOG.md +10 -0
- {bathos-0.3.0 → bathos-0.4.0}/CLAUDE.md +43 -26
- {bathos-0.3.0 → bathos-0.4.0}/PKG-INFO +49 -3
- {bathos-0.3.0 → bathos-0.4.0}/README.md +48 -2
- {bathos-0.3.0 → bathos-0.4.0}/agent_assets/docs/claude-code-integration.md +7 -3
- {bathos-0.3.0 → bathos-0.4.0}/agent_assets/using_bathos/SKILL.md +2 -2
- {bathos-0.3.0 → bathos-0.4.0}/pyproject.toml +1 -1
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/__init__.py +1 -1
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/catalog.py +2 -2
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/cli.py +24 -2
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/compact.py +1 -1
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/config.py +2 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/mcp.py +1 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/migrate.py +56 -1
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/schema.py +3 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/sync.py +41 -6
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_catalog.py +1 -1
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_cli.py +2 -2
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_cluster_workflow.py +3 -3
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_integration.py +2 -2
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_query.py +1 -1
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_remote.py +1 -1
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_sync.py +47 -2
- bathos-0.4.0/tests/test_sync_project_filter.py +212 -0
- {bathos-0.3.0 → bathos-0.4.0}/uv.lock +1 -1
- {bathos-0.3.0 → bathos-0.4.0}/.agent/workspace.id +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/.bth.toml +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/.coverage +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/.github/SECRETS.md +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/.github/workflows/publish.yml +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/.github/workflows/test.yml +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/.gitignore +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/.python-version +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/.readthedocs.yaml +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/LICENSE +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/agent_assets/docs/gemini-cli-integration.md +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/Makefile +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/source/api.rst +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/source/architecture.rst +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/source/conf.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/source/contributing.rst +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/source/index.rst +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/source/slurm-integration.rst +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/source/user-guide.rst +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/superpowers/plans/2026-05-15-bathos-v01-core.md +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/superpowers/plans/2026-05-19-agent-onboarding-export.md +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/superpowers/plans/2026-05-19-decorator.md +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/superpowers/plans/2026-05-19-historical-migration.md +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/superpowers/plans/2026-05-19-linter.md +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/superpowers/plans/2026-05-19-remote-management.md +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/superpowers/plans/2026-05-19-schema-versioning.md +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/superpowers/plans/2026-05-19-skill-and-export-mcp.md +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/superpowers/plans/2026-05-19-v02-feature-wiring.md +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/superpowers/plans/2026-05-20-agentic-integrity-design.md +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/superpowers/plans/2026-05-20-agentic-integrity-impl-plan.md +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/docs/superpowers/specs/2026-05-15-bathos-design.md +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/inventory.txt +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/main.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/praxia.toml +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/scripts/benchmarks/.bth.toml +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/scripts/experiments/.bth.toml +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/scripts/slurm/_bth_env.sh +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/archive.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/campaigns.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/checker.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/decorators.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/export.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/git.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/init.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/linter.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/new_experiment.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/prereg.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/query.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/remote.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/runner.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/sidecar.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/sprint_audit.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/templates/_bth_env.sh +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/src/bathos/validate.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/__init__.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/conftest.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_archive.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_campaigns.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_checker.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_compact.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_config.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_decorators.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_export.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_git.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_init.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_linter.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_mcp_integration.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_mcp_server.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_migrate.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_new_experiment.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_prereg.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_runner.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_schema.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_sidecar.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_slurm_integration.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_sprint_audit.py +0 -0
- {bathos-0.3.0 → bathos-0.4.0}/tests/test_validate.py +0 -0
|
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **`bth sync` indefinite hang** — `sync_catalog()` now passes SSH options (`ConnectTimeout=10`, `BatchMode=yes`) to rsync so unreachable hosts fail in ≤10 s instead of blocking forever; adds `subprocess.run(timeout=120)` as a safety net; raises `RuntimeError` with actionable message on timeout; drops silent `--progress` flag (output was captured, not displayed)
|
|
13
|
+
|
|
14
|
+
- **Parquet timestamp timezone-aware fix** — Ensure timestamps loaded from Parquet are timezone-aware (UTC) to avoid comparisons between naive and aware datetimes in query functions; prevents TypeError and incorrect filtering.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
8
18
|
## [0.3.0] - 2026-05-20
|
|
9
19
|
|
|
10
20
|
### Added
|
|
@@ -10,15 +10,21 @@ bathos is a standalone experiment tracking CLI for a single researcher across 10
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
## Current Status (as of 2026-05-
|
|
13
|
+
## Current Status (as of 2026-05-21)
|
|
14
14
|
|
|
15
|
-
**v0.
|
|
15
|
+
**v0.4.0: complete and merged to main.** 333 tests passing.
|
|
16
16
|
|
|
17
17
|
- Full design spec: `.praxia/specs/bathos-design.md`
|
|
18
18
|
- v0.1 implementation plan: `.praxia/specs/bathos-v01-plan.md`
|
|
19
|
-
- Backlog: items #124–
|
|
19
|
+
- Backlog: items #124–142 in praxia DB (see below); #124–141 done
|
|
20
20
|
|
|
21
|
-
**
|
|
21
|
+
**Shipped in v0.2:** FastMCP server (#128), `@bth.experiment` decorator (#129), `bth check` (#130), SLURM `_bth_env.sh` (#131), `bth new-experiment` (#132), `uv tool` packaging (#133), `bth lint` (#134), `bth migrate` (#135), `bth sync` (#138), schema versioning v2 (#140), `bth archive` (#141), `bth remote` subcommands, `bth export`, `bth catalog-version`.
|
|
22
|
+
|
|
23
|
+
**Shipped in v0.3:** Agentic integrity gate (`--agent-mode`, `--no-sidecar`, `--derived-from`), lineage tracking (`bth lineage`), campaigns (`bth campaign` subcommands + MCP tools), sprint audit (`bth sprint-audit`), Tier-2 lint checks, schema v3 with campaign/lineage/integrity fields, `--campaign` flag on `bth run`.
|
|
24
|
+
|
|
25
|
+
**Shipped in v0.4:** Per-project sync filtering — `bth sync` now pushes/pulls only the current project's runs (no cross-project contamination). Cool-tier layout changed to `runs/<slug>/run_<uuid>.parquet`. New command `bth migrate-to-project-subdirs` migrates flat catalogs. `sync_filter` config knob; `--no-project-filter` opt-out planned.
|
|
26
|
+
|
|
27
|
+
**Remaining backlog:** #136 (bth-migrate praxia workflow), #137 (global instruction portability), #142 (results management design).
|
|
22
28
|
|
|
23
29
|
---
|
|
24
30
|
|
|
@@ -30,7 +36,7 @@ bathos is a standalone experiment tracking CLI for a single researcher across 10
|
|
|
30
36
|
SLURM job / bth run
|
|
31
37
|
│
|
|
32
38
|
▼ atomic write-then-rename
|
|
33
|
-
cool/ ~/.bth/catalog/runs
|
|
39
|
+
cool/ ~/.bth/catalog/runs/<slug>/run_<uuid>.parquet ← minimal schema, SLURM-safe
|
|
34
40
|
│
|
|
35
41
|
bth compact (lazy on query or explicit)
|
|
36
42
|
│
|
|
@@ -52,7 +58,7 @@ Cool schema is intentionally minimal (13 provenance fields). Warm adds `metadata
|
|
|
52
58
|
|
|
53
59
|
### Pre-Registration Schemas (per script directory)
|
|
54
60
|
|
|
55
|
-
Every script in `scripts/experiments/` and `scripts/benchmarks/`
|
|
61
|
+
Every script in `scripts/experiments/` and `scripts/benchmarks/` should have a companion sidecar **`<script-stem>.bth.toml`** declaring its hypothesis, expected outcomes, and result schema. In v0.2+, `bth run` enforces this by default; pass `--no-sidecar` to bypass.
|
|
56
62
|
|
|
57
63
|
**Sidecar format (experiments):**
|
|
58
64
|
```toml
|
|
@@ -118,7 +124,7 @@ fix = "str"
|
|
|
118
124
|
| Catalog storage | **DuckDB + Parquet** (not pyiceberg) | pyiceberg is for distributed data lakes; DuckDB+Parquet is simpler, no overhead |
|
|
119
125
|
| FastMCP | Mirror CLI tool-for-tool | `cli.py` and `mcp.py` both thin layers over same core modules |
|
|
120
126
|
|
|
121
|
-
### Source Layout
|
|
127
|
+
### Source Layout
|
|
122
128
|
|
|
123
129
|
```
|
|
124
130
|
src/bathos/
|
|
@@ -133,8 +139,20 @@ src/bathos/
|
|
|
133
139
|
init.py # bth init logic
|
|
134
140
|
runner.py # bth run subprocess wrapper
|
|
135
141
|
query.py # list_runs, get_run, find_runs, run_sql
|
|
136
|
-
checker.py # bth check — git-drift
|
|
137
|
-
sidecar.py # .bth.toml sidecar parse, content-hash, outcome eval
|
|
142
|
+
checker.py # bth check — git-drift freshness gate
|
|
143
|
+
sidecar.py # .bth.toml sidecar parse, content-hash, outcome eval
|
|
144
|
+
campaigns.py # bth campaign subcommands + Campaign dataclass
|
|
145
|
+
sprint_audit.py # bth sprint-audit cross-project audit
|
|
146
|
+
linter.py # bth lint — Tier-1 and Tier-2 checks
|
|
147
|
+
migrate.py # bth migrate — schema version upgrades
|
|
148
|
+
new_experiment.py # bth new-experiment scaffold
|
|
149
|
+
export.py # bth export — skill + MCP registration
|
|
150
|
+
validate.py # sidecar structural validation
|
|
151
|
+
remote.py # bth remote subcommands
|
|
152
|
+
sync.py # bth sync rsync wrapper
|
|
153
|
+
archive.py # bth archive warm→cold export
|
|
154
|
+
prereg.py # agentic integrity pre-registration gate
|
|
155
|
+
decorators.py # @bth.experiment provenance decorator
|
|
138
156
|
templates/
|
|
139
157
|
_bth_env.sh # SLURM env helper template
|
|
140
158
|
experiment.py # bth new-experiment script skeleton
|
|
@@ -150,25 +168,24 @@ src/bathos/
|
|
|
150
168
|
| 125 | `bth init` — dirs, .bth.toml, catalog bootstrap, SLURM config | P1 | 124 |
|
|
151
169
|
| 126 | `bth run` — CLI wrapper, provenance capture | P1 | 124 |
|
|
152
170
|
| 127 | `bth ls` / `show` / `find` / `sql` — query interface | P1 | 126 |
|
|
153
|
-
| 128 | FastMCP server — mirrors CLI tool-for-tool | P1 | 126, 127, 130 |
|
|
154
|
-
| 129 | `@bth.experiment` — provenance decorator for Typer scripts | P2 | 124 |
|
|
155
|
-
| 130 | `bth check` — freshness/validity vs git HEAD | P2 | 126 |
|
|
156
|
-
| 131 | SLURM `_bth_env.sh` integration | P2 | 125, 126 |
|
|
157
|
-
| 132 | `bth new-experiment` — Typer + sidecar scaffold | P2 | 125 |
|
|
158
|
-
| 133 | `uv tool` packaging + release | P3 | 126–130 |
|
|
159
|
-
| 134 | Script convention linter | P3 | — |
|
|
160
|
-
| 135 | `bth migrate` — Phase 1 mechanical (existing projects) | P2 | 125 |
|
|
161
|
-
| 136 | `bth-migrate` praxia workflow — agentic classification + git mv plan | P2 | 135 |
|
|
162
|
-
| 137 | Global instruction portability (separate design session needed) | P2 | — |
|
|
163
|
-
| 138 | `bth sync` — rsync cool-tier catalog to/from cluster remote | P2 | 125, v0.1 done |
|
|
164
|
-
| 139 | `bth compact` + warm-tier DuckDB (`bth sql` catalog queries) | P2 | v0.1 done |
|
|
165
|
-
| 140 | Schema versioning + extended provenance (hostname, slurm_job_id, metadata JSON, migrations) | P2 | 139 |
|
|
166
|
-
| 141 | `bth archive` — warm→cold partitioned Parquet export | P3 | 139, 140 |
|
|
167
|
-
| 142 | Results management — output convention, file-count utilities, direct management interface design | P2 | 139 |
|
|
171
|
+
| 128 | FastMCP server — mirrors CLI tool-for-tool | P1 ✓ done (v0.2) | 126, 127, 130 |
|
|
172
|
+
| 129 | `@bth.experiment` — provenance decorator for Typer scripts | P2 ✓ done (v0.2) | 124 |
|
|
173
|
+
| 130 | `bth check` — freshness/validity vs git HEAD | P2 ✓ done (v0.1/v0.2) | 126 |
|
|
174
|
+
| 131 | SLURM `_bth_env.sh` integration | P2 ✓ done (v0.1) | 125, 126 |
|
|
175
|
+
| 132 | `bth new-experiment` — Typer + sidecar scaffold | P2 ✓ done (v0.2) | 125 |
|
|
176
|
+
| 133 | `uv tool` packaging + release | P3 ✓ done (v0.2) | 126–130 |
|
|
177
|
+
| 134 | Script convention linter | P3 ✓ done (v0.2) | — |
|
|
178
|
+
| 135 | `bth migrate` — Phase 1 mechanical (existing projects) | P2 ✓ done (v0.2) | 125 |
|
|
179
|
+
| 136 | `bth-migrate` praxia workflow — agentic classification + git mv plan | P2 (not started) | 135 |
|
|
180
|
+
| 137 | Global instruction portability (separate design session needed) | P2 (not started) | — |
|
|
181
|
+
| 138 | `bth sync` — rsync cool-tier catalog to/from cluster remote | P2 ✓ done (v0.2) | 125, v0.1 done |
|
|
182
|
+
| 139 | `bth compact` + warm-tier DuckDB (`bth sql` catalog queries) | P2 ✓ done (v0.1) | v0.1 done |
|
|
183
|
+
| 140 | Schema versioning + extended provenance (hostname, slurm_job_id, metadata JSON, migrations) | P2 ✓ done (v0.2) | 139 |
|
|
184
|
+
| 141 | `bth archive` — warm→cold partitioned Parquet export | P3 ✓ done (v0.2) | 139, 140 |
|
|
185
|
+
| 142 | Results management — output convention, file-count utilities, direct management interface design | P2 (not started) | 139 |
|
|
168
186
|
|
|
169
187
|
**Not yet backlogged:**
|
|
170
|
-
-
|
|
171
|
-
- Outcome evaluation + `runs.outcome` column in warm DuckDB (add as P2, depends on 139)
|
|
188
|
+
- *(none — sidecar enforcement and outcome evaluation shipped in v0.2/v0.3)*
|
|
172
189
|
|
|
173
190
|
---
|
|
174
191
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bathos
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Local-first, zero-server experiment tracking for researchers
|
|
5
5
|
Project-URL: Homepage, https://github.com/mariellerossi/bathos
|
|
6
6
|
Project-URL: Documentation, https://bathos.readthedocs.io
|
|
@@ -66,13 +66,59 @@ bth run scripts/experiments/benchmark_tip3p.py -- --n-steps 1000 --out outputs/r
|
|
|
66
66
|
bth ls --since 7d
|
|
67
67
|
bth show <run-id>
|
|
68
68
|
|
|
69
|
-
# Check
|
|
70
|
-
bth check outputs
|
|
69
|
+
# Check catalog runs for git-drift freshness
|
|
70
|
+
bth check [--status <status>] [--check-outputs]
|
|
71
71
|
|
|
72
72
|
# Escape hatch: raw DuckDB SQL against the catalog
|
|
73
73
|
bth sql "SELECT project_slug, count(*) FROM runs GROUP BY 1"
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
### `bth run` flags
|
|
77
|
+
|
|
78
|
+
**v0.3+ flags:**
|
|
79
|
+
- `--agent-mode collaborative|autonomous` — declare whether this run is agent-driven
|
|
80
|
+
- `--derived-from <run-id>` — record parent run for lineage tracking
|
|
81
|
+
- `--campaign <campaign-id>` — associate this run with a campaign
|
|
82
|
+
- `--no-sidecar` — bypass sidecar enforcement (use for ad-hoc runs)
|
|
83
|
+
|
|
84
|
+
## CLI Reference
|
|
85
|
+
|
|
86
|
+
**Core commands:**
|
|
87
|
+
- `bth init` — initialize bathos in a project
|
|
88
|
+
- `bth run` — execute and track an experiment
|
|
89
|
+
- `bth ls` — list recent runs
|
|
90
|
+
- `bth show <run-id>` — display run details
|
|
91
|
+
- `bth find` — search runs by criteria
|
|
92
|
+
- `bth sql` — raw DuckDB query against catalog
|
|
93
|
+
- `bth check` — check catalog runs for git-drift freshness
|
|
94
|
+
- `bth lineage <run-id>` — show ancestor chain following parent_run_id links
|
|
95
|
+
- `bth sprint-audit [--hours N]` — audit recent runs across all registered projects
|
|
96
|
+
- `bth lint [--project-root PATH]` — check scripts/ for naming conventions and missing sidecars
|
|
97
|
+
- `bth new-experiment <name>` — scaffold a new experiment script and sidecar
|
|
98
|
+
- `bth migrate` — upgrade cool-tier Parquet fragments to current schema version
|
|
99
|
+
- `bth migrate-to-project-subdirs [--dry-run]` — move flat catalog runs into `runs/<slug>/` subdirs (v0.4+)
|
|
100
|
+
- `bth export` — export the using-bathos skill and register MCP server
|
|
101
|
+
|
|
102
|
+
**`bth sync`** — Sync cool-tier catalog to/from cluster (v0.4+: per-project filtered)
|
|
103
|
+
- `bth sync [<remote>]` — push only this project's runs to the remote (filtered by `project_slug`)
|
|
104
|
+
- `bth sync [<remote>] --pull` — pull only this project's runs from the remote
|
|
105
|
+
- Output: `Pushed 47 runs (filtered 275 from other projects) to 'engaging' in 1.2s`
|
|
106
|
+
- Config: `sync_filter = "none"` in `.bth.toml` disables filtering (pushes all projects)
|
|
107
|
+
|
|
108
|
+
**`bth remote`** — Manage sync remotes
|
|
109
|
+
- `bth remote add <name> <url>` — add an SSH remote for catalog sync
|
|
110
|
+
- `bth remote list` — list configured remotes
|
|
111
|
+
- `bth remote remove <name>` — remove a remote
|
|
112
|
+
- `bth remote test <name>` — test SSH connectivity to a remote
|
|
113
|
+
|
|
114
|
+
**`bth campaign`** — Manage experiment campaigns (v0.3+)
|
|
115
|
+
- `bth campaign create <id> --hypothesis <text>` — create a new campaign
|
|
116
|
+
- `bth campaign add <run-id> --campaign <id>` — associate a run with a campaign
|
|
117
|
+
- `bth campaign ls` — list campaigns
|
|
118
|
+
- `bth campaign show <id>` — show campaign details and runs
|
|
119
|
+
- `bth campaign review <id>` — statistical summary and anomaly detection
|
|
120
|
+
- `bth campaign conclude <id> --outcome <label>` — close campaign with outcome
|
|
121
|
+
|
|
76
122
|
## Script conventions
|
|
77
123
|
|
|
78
124
|
`bth init` scaffolds the following structure in your project:
|
|
@@ -30,13 +30,59 @@ bth run scripts/experiments/benchmark_tip3p.py -- --n-steps 1000 --out outputs/r
|
|
|
30
30
|
bth ls --since 7d
|
|
31
31
|
bth show <run-id>
|
|
32
32
|
|
|
33
|
-
# Check
|
|
34
|
-
bth check outputs
|
|
33
|
+
# Check catalog runs for git-drift freshness
|
|
34
|
+
bth check [--status <status>] [--check-outputs]
|
|
35
35
|
|
|
36
36
|
# Escape hatch: raw DuckDB SQL against the catalog
|
|
37
37
|
bth sql "SELECT project_slug, count(*) FROM runs GROUP BY 1"
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
### `bth run` flags
|
|
41
|
+
|
|
42
|
+
**v0.3+ flags:**
|
|
43
|
+
- `--agent-mode collaborative|autonomous` — declare whether this run is agent-driven
|
|
44
|
+
- `--derived-from <run-id>` — record parent run for lineage tracking
|
|
45
|
+
- `--campaign <campaign-id>` — associate this run with a campaign
|
|
46
|
+
- `--no-sidecar` — bypass sidecar enforcement (use for ad-hoc runs)
|
|
47
|
+
|
|
48
|
+
## CLI Reference
|
|
49
|
+
|
|
50
|
+
**Core commands:**
|
|
51
|
+
- `bth init` — initialize bathos in a project
|
|
52
|
+
- `bth run` — execute and track an experiment
|
|
53
|
+
- `bth ls` — list recent runs
|
|
54
|
+
- `bth show <run-id>` — display run details
|
|
55
|
+
- `bth find` — search runs by criteria
|
|
56
|
+
- `bth sql` — raw DuckDB query against catalog
|
|
57
|
+
- `bth check` — check catalog runs for git-drift freshness
|
|
58
|
+
- `bth lineage <run-id>` — show ancestor chain following parent_run_id links
|
|
59
|
+
- `bth sprint-audit [--hours N]` — audit recent runs across all registered projects
|
|
60
|
+
- `bth lint [--project-root PATH]` — check scripts/ for naming conventions and missing sidecars
|
|
61
|
+
- `bth new-experiment <name>` — scaffold a new experiment script and sidecar
|
|
62
|
+
- `bth migrate` — upgrade cool-tier Parquet fragments to current schema version
|
|
63
|
+
- `bth migrate-to-project-subdirs [--dry-run]` — move flat catalog runs into `runs/<slug>/` subdirs (v0.4+)
|
|
64
|
+
- `bth export` — export the using-bathos skill and register MCP server
|
|
65
|
+
|
|
66
|
+
**`bth sync`** — Sync cool-tier catalog to/from cluster (v0.4+: per-project filtered)
|
|
67
|
+
- `bth sync [<remote>]` — push only this project's runs to the remote (filtered by `project_slug`)
|
|
68
|
+
- `bth sync [<remote>] --pull` — pull only this project's runs from the remote
|
|
69
|
+
- Output: `Pushed 47 runs (filtered 275 from other projects) to 'engaging' in 1.2s`
|
|
70
|
+
- Config: `sync_filter = "none"` in `.bth.toml` disables filtering (pushes all projects)
|
|
71
|
+
|
|
72
|
+
**`bth remote`** — Manage sync remotes
|
|
73
|
+
- `bth remote add <name> <url>` — add an SSH remote for catalog sync
|
|
74
|
+
- `bth remote list` — list configured remotes
|
|
75
|
+
- `bth remote remove <name>` — remove a remote
|
|
76
|
+
- `bth remote test <name>` — test SSH connectivity to a remote
|
|
77
|
+
|
|
78
|
+
**`bth campaign`** — Manage experiment campaigns (v0.3+)
|
|
79
|
+
- `bth campaign create <id> --hypothesis <text>` — create a new campaign
|
|
80
|
+
- `bth campaign add <run-id> --campaign <id>` — associate a run with a campaign
|
|
81
|
+
- `bth campaign ls` — list campaigns
|
|
82
|
+
- `bth campaign show <id>` — show campaign details and runs
|
|
83
|
+
- `bth campaign review <id>` — statistical summary and anomaly detection
|
|
84
|
+
- `bth campaign conclude <id> --outcome <label>` — close campaign with outcome
|
|
85
|
+
|
|
40
86
|
## Script conventions
|
|
41
87
|
|
|
42
88
|
`bth init` scaffolds the following structure in your project:
|
|
@@ -38,14 +38,18 @@ The skill is loaded automatically at session start in Claude Code. In a new sess
|
|
|
38
38
|
|
|
39
39
|
| MCP Tool | CLI equivalent | Status |
|
|
40
40
|
|----------|---------------|--------|
|
|
41
|
-
| `
|
|
41
|
+
| `run` | `bth run` | ✅ Available |
|
|
42
42
|
| `list_runs` | `bth ls` | ✅ Available |
|
|
43
43
|
| `get_run` | `bth show <id>` | ✅ Available |
|
|
44
44
|
| `find_runs` | `bth find` | ✅ Available |
|
|
45
45
|
| `run_sql` | `bth sql` | ✅ Available |
|
|
46
46
|
| `compact_catalog` | `bth compact` | ✅ Available |
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
47
|
+
| `check` | `bth check` | ✅ Available |
|
|
48
|
+
| `archive` | `bth archive` | ✅ Available |
|
|
49
|
+
| `campaign_create` | `bth campaign create` | ✅ Available |
|
|
50
|
+
| `campaign_list` | `bth campaign ls` | ✅ Available |
|
|
51
|
+
| `campaign_review` | `bth campaign review` | ✅ Available |
|
|
52
|
+
| `campaign_conclude` | `bth campaign conclude` | ✅ Available |
|
|
49
53
|
|
|
50
54
|
## Update
|
|
51
55
|
|
|
@@ -13,7 +13,7 @@ description: "Use when working in any bathos-tracked research project — experi
|
|
|
13
13
|
|
|
14
14
|
**Scope:** Single researcher, 10+ projects, SLURM cluster integration.
|
|
15
15
|
|
|
16
|
-
**Status:** ✅ v0.
|
|
16
|
+
**Status:** ✅ v0.4.0 shipped — 333 tests passing. Full CLI + MCP server available, including campaigns, agentic integrity gates, and per-project sync filtering.
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
@@ -34,7 +34,7 @@ description: "Use when working in any bathos-tracked research project — experi
|
|
|
34
34
|
| Command | Arguments | Status | Notes |
|
|
35
35
|
|---------|-----------|--------|-------|
|
|
36
36
|
| bth init | --slug, --remote, --slurm-partition | ✅ | Project initialization |
|
|
37
|
-
| bth run | <script> [-- args], --tag, --out, --campaign, --agent-mode, --no-sidecar | ✅ | Execute + provenance capture |
|
|
37
|
+
| bth run | <script> [-- args], --tag, --out, --campaign, --agent-mode, --derived-from, --no-sidecar | ✅ | Execute + provenance capture; v0.3: `--agent-mode` (collaborative/autonomous), `--derived-from` (lineage), `--campaign` (campaign association), `--no-sidecar` (bypass enforcement) |
|
|
38
38
|
| bth ls | --since, --status, --limit, --project | ✅ | List recent runs + OUTCOME column |
|
|
39
39
|
| bth show | <run-id> | ✅ | Full run details + git state |
|
|
40
40
|
| bth find | --project, --since, --status, --tag, --output-file | ✅ | Flexible filtered query |
|
|
@@ -15,7 +15,7 @@ def init_catalog(catalog_dir: Path) -> None:
|
|
|
15
15
|
|
|
16
16
|
def write_run(run: Run, catalog_dir: Path) -> None:
|
|
17
17
|
"""Write (or overwrite) a run record atomically."""
|
|
18
|
-
runs_dir = catalog_dir / "runs"
|
|
18
|
+
runs_dir = catalog_dir / "runs" / run.project_slug
|
|
19
19
|
runs_dir.mkdir(parents=True, exist_ok=True)
|
|
20
20
|
target = runs_dir / f"run_{run.id}.parquet"
|
|
21
21
|
tmp = runs_dir / f"run_{run.id}.tmp.parquet"
|
|
@@ -28,7 +28,7 @@ def read_runs(catalog_dir: Path) -> list[Run]:
|
|
|
28
28
|
runs_dir = catalog_dir / "runs"
|
|
29
29
|
if not runs_dir.exists():
|
|
30
30
|
return []
|
|
31
|
-
parquet_files = list(runs_dir.
|
|
31
|
+
parquet_files = list(runs_dir.rglob("run_*.parquet"))
|
|
32
32
|
if not parquet_files:
|
|
33
33
|
return []
|
|
34
34
|
tables = [pq.read_table(f) for f in parquet_files]
|
|
@@ -595,9 +595,10 @@ def sync(
|
|
|
595
595
|
|
|
596
596
|
try:
|
|
597
597
|
result = sync_catalog(remote, config, catalog_dir, pull=pull)
|
|
598
|
-
direction = "
|
|
598
|
+
direction = "Pulled" if pull else "Pushed"
|
|
599
|
+
filter_msg = f" (filtered {result.filtered} from other projects)" if result.filtered > 0 else ""
|
|
599
600
|
typer.echo(
|
|
600
|
-
f"{direction
|
|
601
|
+
f"{direction} {result.transferred} runs{filter_msg} to/from '{result.remote}' in {result.duration_s:.1f}s"
|
|
601
602
|
)
|
|
602
603
|
except ValueError as e:
|
|
603
604
|
typer.echo(str(e), err=True)
|
|
@@ -624,6 +625,27 @@ def migrate(
|
|
|
624
625
|
typer.echo(" Nothing to migrate.")
|
|
625
626
|
|
|
626
627
|
|
|
628
|
+
@app.command("migrate-to-project-subdirs")
|
|
629
|
+
def migrate_to_subdirs_cmd(
|
|
630
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be moved without writing"),
|
|
631
|
+
):
|
|
632
|
+
"""Move flat cool-tier run parquets into per-project subdirectories.
|
|
633
|
+
|
|
634
|
+
Reads each run's project_slug and moves it to runs/<slug>/run_<uuid>.parquet.
|
|
635
|
+
Run this on both local and remote before using per-project sync filtering.
|
|
636
|
+
"""
|
|
637
|
+
from bathos.migrate import migrate_to_project_subdirs
|
|
638
|
+
|
|
639
|
+
result = migrate_to_project_subdirs(_catalog_dir(), dry_run=dry_run)
|
|
640
|
+
action = "Would move" if dry_run else "Moved"
|
|
641
|
+
typer.echo(f"{action} {result.moved} run(s) into per-project subdirectories.")
|
|
642
|
+
if result.skipped:
|
|
643
|
+
typer.echo(f" {result.skipped} already in place (skipped).")
|
|
644
|
+
if result.by_slug:
|
|
645
|
+
for slug, count in sorted(result.by_slug.items()):
|
|
646
|
+
typer.echo(f" {slug}: {count}")
|
|
647
|
+
|
|
648
|
+
|
|
627
649
|
@app.command("sprint-audit")
|
|
628
650
|
def sprint_audit_cmd(
|
|
629
651
|
hours: int = typer.Option(24, "--hours", help="Lookback window in hours"),
|
|
@@ -180,7 +180,7 @@ def _fragment_count(catalog_dir: Path) -> int:
|
|
|
180
180
|
runs_dir = catalog_dir / "runs"
|
|
181
181
|
if not runs_dir.exists():
|
|
182
182
|
return 0
|
|
183
|
-
return len(list(runs_dir.
|
|
183
|
+
return len(list(runs_dir.rglob("run_*.parquet")))
|
|
184
184
|
|
|
185
185
|
|
|
186
186
|
def should_compact(catalog_dir: Path) -> bool:
|
|
@@ -14,6 +14,7 @@ class ProjectConfig:
|
|
|
14
14
|
catalog_dir: Path = field(default_factory=lambda: Path.home() / ".bth" / "catalog")
|
|
15
15
|
remotes: dict[str, dict] = field(default_factory=dict)
|
|
16
16
|
slurm: dict = field(default_factory=dict)
|
|
17
|
+
sync_filter: str = "project_slug"
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def default_catalog_dir() -> Path:
|
|
@@ -38,6 +39,7 @@ def load_project_config(path: Path) -> ProjectConfig:
|
|
|
38
39
|
catalog_dir=Path(project["catalog_dir"]) if "catalog_dir" in project else default_catalog_dir(),
|
|
39
40
|
remotes=data.get("remotes", {}),
|
|
40
41
|
slurm=data.get("slurm", {}),
|
|
42
|
+
sync_filter=project.get("sync_filter", "project_slug"),
|
|
41
43
|
)
|
|
42
44
|
|
|
43
45
|
|
|
@@ -24,7 +24,7 @@ def migrate_catalog(catalog_dir: Path, dry_run: bool = False) -> MigrateResult:
|
|
|
24
24
|
if not runs_dir.exists():
|
|
25
25
|
return MigrateResult(scanned=0, already_current=0, migrated=0, dry_run=dry_run)
|
|
26
26
|
|
|
27
|
-
fragments = list(runs_dir.
|
|
27
|
+
fragments = list(runs_dir.rglob("run_*.parquet"))
|
|
28
28
|
current_names = {f.name for f in COOL_SCHEMA}
|
|
29
29
|
migrated = 0
|
|
30
30
|
|
|
@@ -62,6 +62,61 @@ def migrate_catalog(catalog_dir: Path, dry_run: bool = False) -> MigrateResult:
|
|
|
62
62
|
)
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
@dataclass
|
|
66
|
+
class MigrateToSubdirsResult:
|
|
67
|
+
moved: int
|
|
68
|
+
skipped: int
|
|
69
|
+
by_slug: dict
|
|
70
|
+
dry_run: bool
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def migrate_to_project_subdirs(catalog_dir: Path, dry_run: bool = False) -> MigrateToSubdirsResult:
|
|
74
|
+
"""Move flat cool-tier run parquets into per-project subdirectories.
|
|
75
|
+
|
|
76
|
+
Reads project_slug from each parquet and moves it to runs/<slug>/run_<uuid>.parquet.
|
|
77
|
+
Files already in a subdir are skipped. Idempotent.
|
|
78
|
+
Falls back to 'default' for parquets without a readable project_slug.
|
|
79
|
+
"""
|
|
80
|
+
from collections import Counter
|
|
81
|
+
|
|
82
|
+
runs_dir = catalog_dir / "runs"
|
|
83
|
+
if not runs_dir.exists():
|
|
84
|
+
return MigrateToSubdirsResult(moved=0, skipped=0, by_slug={}, dry_run=dry_run)
|
|
85
|
+
|
|
86
|
+
# Only consider directly-nested parquets (flat layout), not already-subdir'd ones
|
|
87
|
+
flat_files = [f for f in runs_dir.glob("run_*.parquet")]
|
|
88
|
+
|
|
89
|
+
moves = []
|
|
90
|
+
for parquet in flat_files:
|
|
91
|
+
slug = _read_project_slug(parquet) or "default"
|
|
92
|
+
dst = runs_dir / slug / parquet.name
|
|
93
|
+
moves.append((parquet, dst))
|
|
94
|
+
|
|
95
|
+
if not dry_run:
|
|
96
|
+
for src, dst in moves:
|
|
97
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
src.rename(dst)
|
|
99
|
+
|
|
100
|
+
by_slug = dict(Counter(dst.parent.name for _, dst in moves))
|
|
101
|
+
return MigrateToSubdirsResult(
|
|
102
|
+
moved=len(moves),
|
|
103
|
+
skipped=len(flat_files) - len(moves),
|
|
104
|
+
by_slug=by_slug,
|
|
105
|
+
dry_run=dry_run,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _read_project_slug(parquet: Path) -> str | None:
|
|
110
|
+
"""Read project_slug from the first row of a parquet file."""
|
|
111
|
+
try:
|
|
112
|
+
tbl = pq.read_table(parquet, columns=["project_slug"])
|
|
113
|
+
if tbl.num_rows > 0:
|
|
114
|
+
return tbl["project_slug"][0].as_py()
|
|
115
|
+
except Exception:
|
|
116
|
+
pass
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
|
|
65
120
|
def _default_array(arrow_type: pa.DataType, n: int) -> pa.Array:
|
|
66
121
|
if pa.types.is_string(arrow_type) or pa.types.is_large_string(arrow_type):
|
|
67
122
|
return pa.array([""] * n, type=arrow_type)
|
|
@@ -137,6 +137,9 @@ class Run:
|
|
|
137
137
|
ts = pydict["timestamp"][i]
|
|
138
138
|
if not isinstance(ts, datetime):
|
|
139
139
|
ts = ts.as_py()
|
|
140
|
+
# Ensure timestamp is timezone-aware in UTC for internal comparisons
|
|
141
|
+
if ts.tzinfo is None:
|
|
142
|
+
ts = ts.replace(tzinfo=UTC)
|
|
140
143
|
return cls(
|
|
141
144
|
id=pydict["id"][i],
|
|
142
145
|
project_slug=pydict["project_slug"][i],
|
|
@@ -7,6 +7,8 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
from bathos.config import ProjectConfig
|
|
9
9
|
|
|
10
|
+
_SYNC_TIMEOUT_S = 120 # rsync kill switch: fail if no completion within 2 minutes
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
@dataclass
|
|
12
14
|
class SyncResult:
|
|
@@ -15,6 +17,7 @@ class SyncResult:
|
|
|
15
17
|
transferred: int
|
|
16
18
|
duration_s: float
|
|
17
19
|
remote: str
|
|
20
|
+
filtered: int = 0
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
def sync_catalog(
|
|
@@ -43,8 +46,27 @@ def sync_catalog(
|
|
|
43
46
|
host = remote_config["host"]
|
|
44
47
|
remote_root = remote_config["remote_root"]
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
project_slug = config.slug
|
|
50
|
+
sync_filter = getattr(config, "sync_filter", "project_slug")
|
|
51
|
+
|
|
52
|
+
if sync_filter == "project_slug":
|
|
53
|
+
local_runs = catalog_dir / "runs" / project_slug
|
|
54
|
+
remote_runs = f"{host}:{remote_root}/.bth/catalog/runs/{project_slug}/"
|
|
55
|
+
# Count filtered runs (total in catalog minus this project's runs)
|
|
56
|
+
runs_root = catalog_dir / "runs"
|
|
57
|
+
if not pull and runs_root.exists():
|
|
58
|
+
total = len(list(runs_root.rglob("run_*.parquet")))
|
|
59
|
+
project_count = len(list(local_runs.glob("run_*.parquet"))) if local_runs.exists() else 0
|
|
60
|
+
filtered = total - project_count
|
|
61
|
+
else:
|
|
62
|
+
filtered = 0
|
|
63
|
+
else:
|
|
64
|
+
local_runs = catalog_dir / "runs"
|
|
65
|
+
remote_runs = f"{host}:{remote_root}/.bth/catalog/runs/"
|
|
66
|
+
filtered = 0
|
|
67
|
+
|
|
68
|
+
if not pull:
|
|
69
|
+
local_runs.mkdir(parents=True, exist_ok=True)
|
|
48
70
|
|
|
49
71
|
if pull:
|
|
50
72
|
src = remote_runs
|
|
@@ -53,17 +75,30 @@ def sync_catalog(
|
|
|
53
75
|
src = str(local_runs) + "/"
|
|
54
76
|
dst = remote_runs
|
|
55
77
|
|
|
56
|
-
#
|
|
78
|
+
# ConnectTimeout=10: fail in 10s if host is unreachable rather than hanging
|
|
79
|
+
# BatchMode=yes: never prompt for a password — fail immediately instead
|
|
80
|
+
ssh_opts = "ssh -o ConnectTimeout=10 -o BatchMode=yes"
|
|
81
|
+
|
|
57
82
|
cmd = [
|
|
58
83
|
"rsync",
|
|
59
|
-
"-
|
|
84
|
+
"-az",
|
|
85
|
+
"--partial",
|
|
86
|
+
f"-e{ssh_opts}",
|
|
60
87
|
"--ignore-existing",
|
|
61
88
|
src,
|
|
62
89
|
dst,
|
|
63
90
|
]
|
|
64
91
|
|
|
65
92
|
start_time = time.time()
|
|
66
|
-
|
|
93
|
+
try:
|
|
94
|
+
result = subprocess.run(
|
|
95
|
+
cmd, capture_output=True, text=True, timeout=_SYNC_TIMEOUT_S
|
|
96
|
+
)
|
|
97
|
+
except subprocess.TimeoutExpired:
|
|
98
|
+
raise RuntimeError(
|
|
99
|
+
f"bth sync timed out after {_SYNC_TIMEOUT_S}s — "
|
|
100
|
+
"check that the remote host is reachable and SSH keys are configured"
|
|
101
|
+
)
|
|
67
102
|
duration_s = time.time() - start_time
|
|
68
103
|
|
|
69
104
|
if result.returncode != 0:
|
|
@@ -73,7 +108,7 @@ def sync_catalog(
|
|
|
73
108
|
# rsync outputs something like "Number of regular files transferred: 42"
|
|
74
109
|
transferred = _parse_transferred_count(result.stdout)
|
|
75
110
|
|
|
76
|
-
return SyncResult(transferred=transferred, duration_s=duration_s, remote=remote_name)
|
|
111
|
+
return SyncResult(transferred=transferred, duration_s=duration_s, remote=remote_name, filtered=filtered)
|
|
77
112
|
|
|
78
113
|
|
|
79
114
|
def _parse_transferred_count(rsync_output: str) -> int:
|
|
@@ -24,7 +24,7 @@ def test_write_and_read_single_run(tmp_catalog: Path, sample_run: Run):
|
|
|
24
24
|
def test_write_creates_parquet_file(tmp_catalog: Path, sample_run: Run):
|
|
25
25
|
init_catalog(tmp_catalog)
|
|
26
26
|
write_run(sample_run, tmp_catalog)
|
|
27
|
-
files = list((tmp_catalog / "runs").
|
|
27
|
+
files = list((tmp_catalog / "runs").rglob("*.parquet"))
|
|
28
28
|
assert len(files) == 1
|
|
29
29
|
assert sample_run.id in files[0].name
|
|
30
30
|
|
|
@@ -186,8 +186,8 @@ def test_sql_allows_arbitrary_queries(tmp_path: Path, monkeypatch):
|
|
|
186
186
|
# Create a run to have Parquet files
|
|
187
187
|
runner.invoke(app, ["run", sys.executable, "--", "-c", "pass"])
|
|
188
188
|
|
|
189
|
-
# Query using read_parquet should work without warm DB
|
|
190
|
-
glob = str(catalog / "runs" / "run_*.parquet")
|
|
189
|
+
# Query using read_parquet should work without warm DB (runs are in per-project subdir)
|
|
190
|
+
glob = str(catalog / "runs" / "testproj" / "run_*.parquet")
|
|
191
191
|
result = runner.invoke(app, ["sql", f"SELECT COUNT(*) FROM read_parquet('{glob}')"])
|
|
192
192
|
assert result.exit_code == 0
|
|
193
193
|
assert "1" in result.output
|
|
@@ -119,7 +119,7 @@ remote_root = "~/projects/testproj"
|
|
|
119
119
|
write_run(run2, catalog)
|
|
120
120
|
|
|
121
121
|
# Verify we have 2 cool fragments
|
|
122
|
-
run_files = list((catalog / "runs").
|
|
122
|
+
run_files = list((catalog / "runs").rglob("run_*.parquet"))
|
|
123
123
|
assert len(run_files) == 2, f"Expected 2 run files, got {len(run_files)}"
|
|
124
124
|
|
|
125
125
|
# Step 3: Compact cool fragments → warm DuckDB
|
|
@@ -177,7 +177,7 @@ remote_root = "~/projects/testproj"
|
|
|
177
177
|
|
|
178
178
|
# Verify rsync command structure
|
|
179
179
|
assert cmd[0] == "rsync"
|
|
180
|
-
assert "-
|
|
180
|
+
assert "-az" in cmd
|
|
181
181
|
assert "--ignore-existing" in cmd
|
|
182
182
|
# Should have source and destination with runs/ directories
|
|
183
183
|
assert any("runs/" in str(arg) for arg in cmd)
|
|
@@ -238,7 +238,7 @@ def test_cluster_workflow_with_slurm_job_id(git_repo: Path, monkeypatch):
|
|
|
238
238
|
write_run(run, catalog)
|
|
239
239
|
|
|
240
240
|
# Verify SLURM_JOB_ID is in the written fragment
|
|
241
|
-
run_files = list((catalog / "runs").
|
|
241
|
+
run_files = list((catalog / "runs").rglob("run_*.parquet"))
|
|
242
242
|
assert len(run_files) == 1
|
|
243
243
|
|
|
244
244
|
# Compact and verify slurm_job_id is preserved in warm tier
|