runops 2.6.0__tar.gz → 2.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.
- {runops-2.6.0 → runops-2.7.0}/PKG-INFO +1 -1
- {runops-2.6.0 → runops-2.7.0}/SPEC.md +13 -6
- {runops-2.6.0 → runops-2.7.0}/docs/guide.md +4 -1
- {runops-2.6.0 → runops-2.7.0}/docs/reference.md +5 -4
- {runops-2.6.0 → runops-2.7.0}/pyproject.toml +1 -1
- {runops-2.6.0 → runops-2.7.0}/src/runops/__init__.py +1 -1
- {runops-2.6.0 → runops-2.7.0}/src/runops/cli.py +38 -29
- {runops-2.6.0 → runops-2.7.0}/src/runops/harness.py +2 -2
- {runops-2.6.0 → runops-2.7.0}/src/runops/manifest.py +11 -3
- {runops-2.6.0 → runops-2.7.0}/src/runops/ops/notes.py +6 -6
- {runops-2.6.0 → runops-2.7.0}/src/runops/ops/progress.py +16 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/skills/runops-check-status/SKILL.md +4 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/skills/runops-reference/SKILL.md +3 -3
- {runops-2.6.0 → runops-2.7.0}/tests/test_progress.py +50 -0
- {runops-2.6.0 → runops-2.7.0}/uv.lock +1 -1
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/check/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/improve-harness/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/agents/openai.yaml +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/examples/refactor-request.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/references/anti-patterns.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/references/api-compatibility.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/references/packaging-modernization.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/references/refactor-playbook.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/references/verification.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/scripts/api_surface_snapshot.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/scripts/import_smoke.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/scripts/inspect_python_package.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/scripts/refactor_quality_gate.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/release/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/simulator-adapter/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/spec-reviewer/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.agents/skills/sync-docs/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.claude/agents/spec-reviewer.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.claude/rules/architecture.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.claude/rules/dev-workflow.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.claude/rules/gotchas.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.claude/settings.json +0 -0
- {runops-2.6.0 → runops-2.7.0}/.claude/skills/check/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.claude/skills/improve-harness/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.claude/skills/python-package-refactor/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.claude/skills/release/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.claude/skills/simulator-adapter/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.claude/skills/spec-reviewer/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.claude/skills/sync-docs/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.codex/README.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/.codex/agents/spec-reviewer.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/.codex/config.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/.codex/rules/runops.rules +0 -0
- {runops-2.6.0 → runops-2.7.0}/.dockerignore +0 -0
- {runops-2.6.0 → runops-2.7.0}/.gitattributes +0 -0
- {runops-2.6.0 → runops-2.7.0}/.github/assets/conversation-dark.svg +0 -0
- {runops-2.6.0 → runops-2.7.0}/.github/assets/conversation-light.svg +0 -0
- {runops-2.6.0 → runops-2.7.0}/.github/assets/hero-dark.svg +0 -0
- {runops-2.6.0 → runops-2.7.0}/.github/assets/hero-light.svg +0 -0
- {runops-2.6.0 → runops-2.7.0}/.github/assets/make_readme_art.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/.github/workflows/ci.yml +0 -0
- {runops-2.6.0 → runops-2.7.0}/.github/workflows/publish.yml +0 -0
- {runops-2.6.0 → runops-2.7.0}/.gitignore +0 -0
- {runops-2.6.0 → runops-2.7.0}/.harness/manifest.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/.pre-commit-config.yaml +0 -0
- {runops-2.6.0 → runops-2.7.0}/.vscode/settings.json +0 -0
- {runops-2.6.0 → runops-2.7.0}/AGENTS.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/CLAUDE.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/LICENSE +0 -0
- {runops-2.6.0 → runops-2.7.0}/README.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/docs/adapter.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/adapters/__init__.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/adapters/base.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/adapters/generic.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/adapters/registry.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/case.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/config.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/errors.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/jobgen.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/launchers.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/legacy_adapters/__init__.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/legacy_adapters/beach.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/legacy_adapters/emses.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/ops/__init__.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/ops/analysis.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/ops/common.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/ops/create.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/ops/listing.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/ops/migrate.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/ops/setup.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/ops/skills.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/ops/submit.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/ops/sync.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/ops/tidy.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/project.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/runtime.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/skills/runops-analyze/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/skills/runops-create-run/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/skills/runops-maintain/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/skills/runops-tidy/SKILL.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/slurm.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/src/runops/util.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/__init__.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/conftest.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/.codex/rules/a.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/.runops/test-runs/T20260701-0001/receipt.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/.runops/work/beach-x-abc/bin/beach +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/AGENTS.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/CLAUDE.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/cases/beach/plane/beach.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/cases/beach/plane/case.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/cases/old/beach.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/cases/old/case.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/launchers.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/research/CURRENT.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/runops.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/runs/AGENTS.md +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/runs/_archive/plane/R20260601-0001/manifest.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/runs/plane/R20260701-0001--a/input/beach.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/runs/plane/R20260701-0001--a/manifest.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/runs/plane/R20260702-0001/manifest.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/runs/scan/R20260701-0002/manifest.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/runs/scan/survey.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/simulators.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/site.toml +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/golden/mpi_standard.sh +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/golden/openmp_rsc.sh +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_adapters.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_analysis_layout.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_archive_series.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_build.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_dev_harness.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_jobgen.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_legacy_adapters.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_lifecycle.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_manifest_config.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_migrate.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_mv_naming.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_project_scope.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_run_job.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_safety.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_scratch_tidy.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_setup_misc.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_skills.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_sweep.py +0 -0
- {runops-2.6.0 → runops-2.7.0}/tests/test_sync_evidence.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: runops
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.0
|
|
4
4
|
Summary: Run ledger and Slurm job submission for HPC simulation projects
|
|
5
5
|
Project-URL: Homepage, https://github.com/Nkzono99/runops
|
|
6
6
|
Project-URL: Documentation, https://github.com/Nkzono99/runops/blob/main/docs/reference.md
|
|
@@ -475,8 +475,12 @@ adapter が `progress(run_dir)` を持つとき、`sync` は running の run と
|
|
|
475
475
|
上限 1000 件程度)。queue を離れたときの観測は終了時刻で記録し、終了後の待ち時間を速度に含めない。
|
|
476
476
|
- 要約を `[[job.attempts]].progress` に置く: `done`、`total`、`unit`、`start`、`fraction`、
|
|
477
477
|
`rate_recent_per_hour`(直近 10 分)、`rate_attempt_per_hour`(program 開始から。起点は
|
|
478
|
-
`start`)、`eta_hours`(running
|
|
478
|
+
`start`)、`eta_hours`(running のみ)、`last_change_at`(`done` が最後に増えた観測時刻)、
|
|
479
|
+
`stalled_hours`(最新観測が `last_change_at` から 1 時間以上進んでいないとき、その長さ。
|
|
480
|
+
終了後の観測には付けない)。program 開始時刻は job.sh が stdout に出す
|
|
479
481
|
`runops: start` 行から取り、`started_at` に入れる。
|
|
482
|
+
- `status` / `list` は `stalled_hours` があれば `stalled 6.7h` と表示する。観測は `sync` のときだけ
|
|
483
|
+
なので、停止の検出は `sync` の間隔より細かくならない。
|
|
480
484
|
- `status` / `list` は要約だけを読む(file を開かない)。read-only command は観測を足さない。
|
|
481
485
|
- 解析は adapter が行い、runops は保存と表示だけを持つ(§1.2 #4)。
|
|
482
486
|
|
|
@@ -497,7 +501,8 @@ adapter が `progress(run_dir)` を持つとき、`sync` は running の run と
|
|
|
497
501
|
`--json` は作成した run の同じ summary を返す。summary には `params`、`changed_keys`、
|
|
498
502
|
`parent_run`、`exe_hash`、`input_hash` を含め、run 同士の比較に別 command を要らなくする。
|
|
499
503
|
walltime の根拠として `walltime`、最新 attempt の `elapsed`、`walltime_used`(elapsed / walltime)、
|
|
500
|
-
`slurm_state`(`TIMEOUT`
|
|
504
|
+
`slurm_state`(`TIMEOUT` など)も含める。`list` / `status` の `--brief` は summary から
|
|
505
|
+
`params`、`why`、`last_note` を外す(多数の run を横断して渡すとき用。表では note / why 列を省く)。
|
|
501
506
|
- 破壊的 command(`cancel`、`delete`、`tidy --apply`、`archive`)は対象を表示して
|
|
502
507
|
確認を取る。`-y/--yes` で省略。
|
|
503
508
|
- exit code: 0 成功、1 対象 run の実行時エラー(例: sbatch 失敗)、2 使用方法・設定エラー。
|
|
@@ -518,7 +523,7 @@ adapter が `progress(run_dir)` を持つとき、`sync` は running の run と
|
|
|
518
523
|
| `runo doctor [--json]` | `runops.toml` の妥当性、adapter の import 可否、executable の存在、`sbatch` の有無、skill の鮮度を検査 |
|
|
519
524
|
| `runo update [--skills-only] [-y]` | adapter package を upgrade し、skill を再展開する |
|
|
520
525
|
| `runo build SIM [--force]` | `local_source` simulator を `.runops/build/` に build する(§4.1) |
|
|
521
|
-
| `runo context [--json]` | agent 向け現在地。project 名、simulator、active run
|
|
526
|
+
| `runo context [--json]` | agent 向け現在地。project 名、simulator、active run 数と全 active run(`--brief` と同じ summary、`active`)、直近 run 5 件、scratch 期限切れ数、`LOG.md` 末尾 20 行、現用 group の解析 topic 名(§9.1)。配置した runops skill が install 済みの runops より古ければ `runo update --skills-only` を促す warning |
|
|
522
527
|
| `runo case new NAME --simulator SIM [--from CASE]` | case をスキャフォールド。adapter の `case_template()` を展開 |
|
|
523
528
|
| `runo case list` | case 一覧 |
|
|
524
529
|
| `runo new CASE [--why TEXT] [--label L] [--set k=v]... [--job k=v]... [--tag T]... [--scratch] [--dest DIR] [--submit] [--created-by WHO] [--json]` | 単発 run を作る |
|
|
@@ -528,10 +533,10 @@ adapter が `progress(run_dir)` を持つとき、`sync` は running の run と
|
|
|
528
533
|
| `runo submit --all [DIR] [-y]` | DIR 配下の `created` run をすべて投入。件数と概算 core-hours を表示して確認 |
|
|
529
534
|
| `runo cancel RUN... [--why TEXT] [-y]` | scancel して sync。`--why` は `cancelled: ...` の note として残す |
|
|
530
535
|
| `runo retry RUN [--submit] [-y]` | `failed`/`cancelled` を `created` に戻す。直近の cancel に `--why` があれば理由を示して確認を取る |
|
|
531
|
-
| `runo status [RUN...] [--json]` | manifest の状態を表示。Slurm
|
|
536
|
+
| `runo status [RUN...] [--brief] [--json]` | manifest の状態を表示。Slurm を呼ばない。`--brief` は §7.1 |
|
|
532
537
|
| `runo sync [RUN...] [--json]` | squeue/sacct で状態を更新。引数なしは `submitted`/`running` 全件 |
|
|
533
538
|
| `runo log RUN [--err] [-n N] [-f]` | 最新 job の stdout/stderr を表示。`-f` は tail -f |
|
|
534
|
-
| `runo list [PATH] [--all] [--since 30d] [--case C] [--sweep L] [--status S]... [--tag T]... [--scratch] [--archived] [--json]` | run 一覧(§7.4) |
|
|
539
|
+
| `runo list [PATH] [--all] [--since 30d] [--case C] [--sweep L] [--status S]... [--tag T]... [--scratch] [--archived] [--brief] [--json]` | run 一覧(§7.4) |
|
|
535
540
|
| `runo note RUN [TEXT] [--by WHO]` | TEXT があれば `[[notes]]` に追記、なければ notes と `notes.md` を表示 |
|
|
536
541
|
| `runo pin RUN [--off] [--reason TEXT]` | `run.pinned` を切り替える。pinned は `list` に常に出て、`tidy` / `delete` の対象外 |
|
|
537
542
|
| `runo summarize RUN... [--force]` | adapter の `summarize()` を呼び `analysis/summary.json` を書く。既存があれば `--force` なしではスキップ |
|
|
@@ -550,7 +555,8 @@ adapter が `progress(run_dir)` を持つとき、`sync` は running の run と
|
|
|
550
555
|
- 既定で表示しない: scratch run、archived run、上記以外の古い terminal run。
|
|
551
556
|
- `--all` は scratch と archived 以外をすべて表示。`--scratch`、`--archived` は
|
|
552
557
|
それぞれを **追加** する。
|
|
553
|
-
- 表示列: id、status、label、case、sweep、created
|
|
558
|
+
- 表示列: id、status、label、case、sweep、created(相対時刻)、elapsed、progress、最新 note の先頭 60 文字、`why`。
|
|
559
|
+
`--brief` は note と `why` の列を省く。
|
|
554
560
|
- sort は `created_at` 降順。
|
|
555
561
|
- `PATH` を与えた場合はその directory 配下に限定する。
|
|
556
562
|
|
|
@@ -1038,5 +1044,6 @@ v2 の文書は次の 4 ファイルだけにする。
|
|
|
1038
1044
|
| 生成ハーネスの書き方(2026-10-21) | AGENTS.md の Rules を「作業の進め方」5 項目に絞り、各項目を「何をするか」と短い理由で書く。skill は手順に専念し、AGENTS.md の方針(manifest を手で編集しない等)を繰り返さない | #112・#113 で規則が増え、禁止形(never / do not)が並んでいた。現行の Claude の prompting 指針は、しないことより何をするかを、理由と共に書くことを勧め、強い禁止語は過剰反応を招くとしている |
|
|
1039
1045
|
| 複数 run の mv / archive(2026-10-22) | 1 回の `run.lock` の下で全対象を読み直して検査し、通ってから移す。途中の filesystem error では移し終えた run id を示して止まる | `mv A B C --dest X` は 1 件ずつ検査して移していたので、途中の submitted run で一部だけ移動していた。archive は表示の後で lock を取り直していて、その間に再投入された run で同じことが起き得た |
|
|
1040
1046
|
| 進捗と系列配置 2.5.0(2026-10-23) | adapter に任意の `progress()`、`sync` が時刻付き観測を保存し速度・残り時間を要約(§6.4)。`new --from` の既定置き場所を元 run と同じ directory に。agent context の `docs/` も展開 | 速度比較のために agent が時刻付き batch 観測を手で保存し、再開点や終了後の待ちを毎回補正していた(4 回)。継続世代が case 直下に並び、同じ計算の系列を見分けにくかった。skill が package の `docs/` を参照していたが展開されていなかった。進捗の解析は simulator 固有なので adapter が持ち、runops は保存と表示だけにする |
|
|
1047
|
+
| 横断の状態確認 2.7.0(2026-10-30) | `list` / `status` に `--brief`、`context` に全 active run の `active`、progress 要約に `last_change_at` と `stalled_hours`(§6.4、§7.1、§7.3) | hub で 4 project の active run を集めるのに `recent_runs` 5 件では足りず、全 `params` 付きの `list --json` は shell 引数長を超えた(3 回)。進捗が 6 時間止まった run を 49% と報告した。新しい command は足さず、既存の summary を小さくし、観測から止まりを出す |
|
|
1041
1048
|
| FEEDBACK 棚卸し 2.4.0(2026-10-23) | `--job k=v` と `new --from` の `[job]` 継承、created の archive / restore、archived scratch を期限削除しない、展開 skill の references 参照の書き換えと `doctor` のリンク検査、adapter warning への版の併記、生成 AGENTS.md に解析環境の置き場所 | hub の FEEDBACK で、資源変更のための case 編集(3 回)、置換済み draft を archive できず helper で迂回(2 回)、保管目的の archive が期限削除される、skill から reference に辿れない、新版の正規キーが誤記に見える、解析依存の置き場所が project ごとに違う、が記録された。いずれも既存 command の拡張で、command 数は増やさない |
|
|
1042
1049
|
| 使い方ガイド | `docs/guide.md` を追加し、文書を 4 ファイルにする(§13.3) | README の依頼例の表と command 一覧の間を埋める、場面ごとの流れ(case と run の分け方、smoke から本計算、状態の決まり方、整理)の置き場所が無かった |
|
|
@@ -134,7 +134,10 @@ walltime は、同じ case・同じ資源で終わった run の実時間を元
|
|
|
134
134
|
adapter が進捗に対応していれば(BEACH の batch、MPIEMSES3D の step)、`sync` のたびに時刻付きの
|
|
135
135
|
観測を残し、`runo list` / `status` に `62% 0.198 s/batch eta 3.1h` のように進み具合、直近 10 分の
|
|
136
136
|
速度、残り時間を出します。resume した run は再開点から数え、終わった run は終了時刻までで速度を出すので、
|
|
137
|
-
版を切り替えた前後の比較にそのまま使えます(`--json` の `progress
|
|
137
|
+
版を切り替えた前後の比較にそのまま使えます(`--json` の `progress`)。`sync` を重ねても進みが 1 時間以上
|
|
138
|
+
変わらない run には `stalled 6.7h` が付くので、Slurm では RUNNING でも止まっている run を見分けられます。
|
|
139
|
+
全 project を横断して見るときは `runo context --json` の `active`(全 active run)か `runo list --brief --json`
|
|
140
|
+
(`params` や note を省いた小さい JSON)を使います。
|
|
138
141
|
|
|
139
142
|
### 状態の決まり方
|
|
140
143
|
|
|
@@ -141,7 +141,8 @@ run id は削除後も再利用しません。`--dest` に既存 run の中は
|
|
|
141
141
|
`list --json` と `status --json` は、walltime の根拠として `walltime`、最新 attempt の `elapsed`、
|
|
142
142
|
`walltime_used`(elapsed / walltime)、`slurm_state` も返します。adapter が `progress()` を持てば
|
|
143
143
|
`progress`(`done`、`total`、`unit`、`fraction`、`rate_recent_per_hour`、`rate_attempt_per_hour`、
|
|
144
|
-
`eta_hours`)も返し、表の `progress` 列に `62% 0.198 s/batch eta 3.1h`
|
|
144
|
+
`eta_hours`、`last_change_at`、`stalled_hours`)も返し、表の `progress` 列に `62% 0.198 s/batch eta 3.1h` と出します。
|
|
145
|
+
`sync` の観測で `done` が 1 時間以上増えていなければ `stalled 6.7h` が付きます(観測は `sync` のときだけ)。表では `TIMEOUT` などを
|
|
145
146
|
`failed (TIMEOUT)` と示します。
|
|
146
147
|
|
|
147
148
|
全 command は `--help` で option を確認できます。`RUN` には run id、run directory、
|
|
@@ -153,15 +154,15 @@ run id は削除後も再利用しません。`--dest` に既存 run の中は
|
|
|
153
154
|
| `runo doctor [--json]` | 設定、adapter の解決経路、executable、sbatch、skill の鮮度とリンク切れを点検 |
|
|
154
155
|
| `runo update [--skills-only] [-y]` | simulator package を更新し skill と AGENTS.md の管理 block を再生成 |
|
|
155
156
|
| `runo build SIM [--force]` | `local_source` の simulator を build |
|
|
156
|
-
| `runo context [--json]` | agent
|
|
157
|
+
| `runo context [--json]` | agent 向けの現在地。全 active run(`active`、`--brief` と同じ形)、直近 5 run、現用 group の解析 topic 名(JSON の `group_analysis` は group path → topic 名一覧) |
|
|
157
158
|
| `runo case new NAME [-s SIM] [--from CASE]` / `runo case list` | case の作成と一覧 |
|
|
158
159
|
| `runo new CASE [--why] [--label] [--set k=v]... [--job k=v]... [--tag] [--scratch] [--dest DIR] [--submit] [--json]` | run を 1 つ作る。`--job` はこの run の `[job]` の値 |
|
|
159
160
|
| `runo new --from RUN [--set k=v]... [--job k=v]... [--job-from-case] [--continue]` | 既存 run の params と `[job]` から派生(`--job-from-case` で `[job]` は case から)。置き場所の既定は元 run と同じ directory。`--continue` は adapter の restart 対応が必要 |
|
|
160
161
|
| `runo sweep CASE --set k=v1,v2 ... [--names k=n1,n2...] [--job k=v]... [--label] [--dest DIR] [--limit N] [--apply] [--submit] [--json]` | 直積の候補表示。`--apply` で作成。`--names` は各値の短い label、`--dest` は sweep directory の親 |
|
|
161
162
|
| `runo submit RUN... [-p P] [--qos Q] [--afterok JOB]` / `runo submit --all [DIR] [-y]` | job.sh を生成して sbatch |
|
|
162
163
|
| `runo cancel RUN... [--why TEXT] [-y]` / `runo retry RUN [--submit] [-y]` | scancel(`--why` は note に残る)/ failed・cancelled を created に戻す(理由付きで cancel された run は確認を取る) |
|
|
163
|
-
| `runo status [RUN...]` / `runo sync [RUN...]` / `runo log RUN [--err] [-n N] [-f]` | 状態表示 / Slurm から更新 / log 表示 |
|
|
164
|
-
| `runo list [PATH] [--all] [--since 30d] [--case] [--sweep] [--status] [--tag] [--scratch] [--archived] [--json]` | run
|
|
164
|
+
| `runo status [RUN...] [--brief]` / `runo sync [RUN...]` / `runo log RUN [--err] [-n N] [-f]` | 状態表示 / Slurm から更新 / log 表示 |
|
|
165
|
+
| `runo list [PATH] [--all] [--since 30d] [--case] [--sweep] [--status] [--tag] [--scratch] [--archived] [--brief] [--json]` | run 一覧。`--brief` は `params`、`why`、note を省く(表では note / why 列) |
|
|
165
166
|
| `runo note RUN [TEXT]` / `runo pin RUN [--off] [--reason]` | ノート追記・表示 / pin |
|
|
166
167
|
| `runo summarize RUN... [--force]` | adapter の要約を `analysis/summary.json` に書く |
|
|
167
168
|
| `runo mv RUN... [--dest DIR] [--label L] [--why TEXT]` | `runs/` 配下で改名・移動。id、入力、sweep、ノートは保つ。submitted / running と archived は不可。全対象を検査してから移し、1 件でも不可なら何も動かさない |
|
|
@@ -257,6 +257,16 @@ def context(as_json: bool = typer.Option(False, "--json")) -> None:
|
|
|
257
257
|
f"runs: {counts or 'none'}; active: {data['active_runs']}; "
|
|
258
258
|
f"expired scratch: {data['expired_scratch']}"
|
|
259
259
|
)
|
|
260
|
+
if data["active"]:
|
|
261
|
+
from runops.ops.progress import format_progress
|
|
262
|
+
|
|
263
|
+
out.print("active:")
|
|
264
|
+
for run in data["active"]:
|
|
265
|
+
out.print(
|
|
266
|
+
f" {run['id']} {run['status']:9} {run['case']} {run['label']} "
|
|
267
|
+
f"{format_progress(run['progress'])}"
|
|
268
|
+
)
|
|
269
|
+
out.print("recent:")
|
|
260
270
|
for run in data["recent_runs"]:
|
|
261
271
|
out.print(
|
|
262
272
|
f" {run['id']} {run['status']:9} {run['case']} {run['label']} {run['why'][:60]}"
|
|
@@ -592,23 +602,14 @@ def retry(
|
|
|
592
602
|
_submit_each(project, [m])
|
|
593
603
|
|
|
594
604
|
|
|
595
|
-
def _status_table(project: Project, manifests: list[Manifest]) -> Table:
|
|
605
|
+
def _status_table(project: Project, manifests: list[Manifest], *, brief: bool = False) -> Table:
|
|
596
606
|
from runops.ops.listing import relative_age
|
|
597
607
|
from runops.ops.progress import format_progress
|
|
598
608
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
"
|
|
602
|
-
|
|
603
|
-
"case",
|
|
604
|
-
"sweep",
|
|
605
|
-
"created",
|
|
606
|
-
"elapsed",
|
|
607
|
-
"progress",
|
|
608
|
-
"note",
|
|
609
|
-
"why",
|
|
610
|
-
box=None,
|
|
611
|
-
)
|
|
609
|
+
columns = ["id", "status", "label", "case", "sweep", "created", "elapsed", "progress"]
|
|
610
|
+
if not brief:
|
|
611
|
+
columns += ["note", "why"]
|
|
612
|
+
table = Table(*columns, box=None)
|
|
612
613
|
for m in manifests:
|
|
613
614
|
s = m.to_summary(project.root)
|
|
614
615
|
color = {
|
|
@@ -630,7 +631,7 @@ def _status_table(project: Project, manifests: list[Manifest]) -> Table:
|
|
|
630
631
|
status += " [dim](pinned)[/]"
|
|
631
632
|
if s["superseded_by"]:
|
|
632
633
|
status += f" [dim](-> {', '.join(s['superseded_by'])})[/]"
|
|
633
|
-
|
|
634
|
+
row = [
|
|
634
635
|
s["id"],
|
|
635
636
|
status,
|
|
636
637
|
s["label"],
|
|
@@ -639,15 +640,19 @@ def _status_table(project: Project, manifests: list[Manifest]) -> Table:
|
|
|
639
640
|
relative_age(s["created_at"]),
|
|
640
641
|
elapsed,
|
|
641
642
|
format_progress(s["progress"]),
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
643
|
+
]
|
|
644
|
+
if not brief:
|
|
645
|
+
row += [s["last_note"][:60], s["why"][:60]]
|
|
646
|
+
table.add_row(*row)
|
|
645
647
|
return table
|
|
646
648
|
|
|
647
649
|
|
|
648
650
|
@app.command()
|
|
649
651
|
def status(
|
|
650
652
|
runs: Optional[list[str]] = typer.Argument(None),
|
|
653
|
+
brief: bool = typer.Option(
|
|
654
|
+
False, "--brief", help="Leave out params, why, notes and the note/why columns."
|
|
655
|
+
),
|
|
651
656
|
as_json: bool = typer.Option(False, "--json"),
|
|
652
657
|
) -> None:
|
|
653
658
|
"""Show manifest state (no Slurm query). Without RUN: active runs."""
|
|
@@ -662,18 +667,19 @@ def status(
|
|
|
662
667
|
if as_json:
|
|
663
668
|
data = []
|
|
664
669
|
for m in targets:
|
|
665
|
-
s = m.to_summary(project.root)
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
670
|
+
s = m.to_summary(project.root, brief=brief)
|
|
671
|
+
if not brief:
|
|
672
|
+
s.update(
|
|
673
|
+
{
|
|
674
|
+
"params": m.params,
|
|
675
|
+
"job": {k: v for k, v in m.job.items() if k != "attempts"},
|
|
676
|
+
"notes": m.notes,
|
|
677
|
+
}
|
|
678
|
+
)
|
|
673
679
|
data.append(s)
|
|
674
680
|
_print_json(data)
|
|
675
681
|
return
|
|
676
|
-
out.print(_status_table(project, targets))
|
|
682
|
+
out.print(_status_table(project, targets, brief=brief))
|
|
677
683
|
|
|
678
684
|
|
|
679
685
|
@app.command("sync")
|
|
@@ -730,6 +736,9 @@ def list_cmd(
|
|
|
730
736
|
tags: Optional[list[str]] = typer.Option(None, "--tag"),
|
|
731
737
|
scratch: bool = typer.Option(False, "--scratch", help="Also show scratch runs."),
|
|
732
738
|
archived: bool = typer.Option(False, "--archived", help="Also show archived runs."),
|
|
739
|
+
brief: bool = typer.Option(
|
|
740
|
+
False, "--brief", help="Leave out params, why and notes (small JSON, short table)."
|
|
741
|
+
),
|
|
733
742
|
as_json: bool = typer.Option(False, "--json"),
|
|
734
743
|
) -> None:
|
|
735
744
|
"""List active and recent runs."""
|
|
@@ -752,12 +761,12 @@ def list_cmd(
|
|
|
752
761
|
)
|
|
753
762
|
runs = select_runs(project, flt)
|
|
754
763
|
if as_json:
|
|
755
|
-
_print_json([m.to_summary(project.root) for m in runs])
|
|
764
|
+
_print_json([m.to_summary(project.root, brief=brief) for m in runs])
|
|
756
765
|
return
|
|
757
766
|
if not runs:
|
|
758
767
|
out.print("no runs (try --all, --scratch, --archived)")
|
|
759
768
|
return
|
|
760
|
-
out.print(_status_table(project, runs))
|
|
769
|
+
out.print(_status_table(project, runs, brief=brief))
|
|
761
770
|
|
|
762
771
|
|
|
763
772
|
@app.command()
|
|
@@ -12,14 +12,14 @@ END = "<!-- runops:end -->"
|
|
|
12
12
|
COMMANDS = """\
|
|
13
13
|
| command | effect |
|
|
14
14
|
|---|---|
|
|
15
|
-
| `runo context --json` | active runs, recent runs, group analysis topics, LOG.md tail |
|
|
15
|
+
| `runo context --json` | all active runs (with progress), recent runs, group analysis topics, LOG.md tail |
|
|
16
16
|
| `runo case list` / `runo case new NAME --simulator SIM [--from CASE]` | list / scaffold cases |
|
|
17
17
|
| `runo new CASE --why TEXT [--set k=v]... [--job k=v]... [--scratch] [--submit]` | create one run |
|
|
18
18
|
| `runo new --from RUN --why TEXT [--set k=v]... [--job k=v]... [--continue]` | derive a run (keeps its params and [job]) |
|
|
19
19
|
| `runo sweep CASE --set k=v1,v2 [--names k=n1,n2] [--apply] [--submit]` | preview / create a parameter product |
|
|
20
20
|
| `runo submit RUN... [--partition P]` / `runo submit --all DIR` | sbatch created runs |
|
|
21
21
|
| `runo sync` / `runo status RUN` / `runo log RUN [--err]` | refresh state from Slurm / show / read logs |
|
|
22
|
-
| `runo list [--all] [--case C] [--status S] [--json]` | recent + active runs (old, scratch, archived hidden) |
|
|
22
|
+
| `runo list [--all] [--case C] [--status S] [--brief] [--json]` | recent + active runs (old, scratch, archived hidden); `--brief` drops params/why/notes |
|
|
23
23
|
| `runo cancel RUN [--why T]` / `runo retry RUN` | stop a job / reset failed or cancelled to created |
|
|
24
24
|
| `runo note RUN "text"` / `runo pin RUN --reason TEXT` | append an observation / protect a key run |
|
|
25
25
|
| `runo summarize RUN` | adapter summary into analysis/summary.json |
|
|
@@ -188,14 +188,18 @@ class Manifest:
|
|
|
188
188
|
notes = self.data.setdefault("notes", [])
|
|
189
189
|
notes.append({"at": now_iso(), "by": by, "text": text})
|
|
190
190
|
|
|
191
|
-
def to_summary(self, root: Path) -> dict[str, Any]:
|
|
192
|
-
"""Flat dict for ``list --json`` / ``status --json``.
|
|
191
|
+
def to_summary(self, root: Path, *, brief: bool = False) -> dict[str, Any]:
|
|
192
|
+
"""Flat dict for ``list --json`` / ``status --json``.
|
|
193
|
+
|
|
194
|
+
*brief* leaves out ``params``, ``why`` and ``last_note``, so a listing of many
|
|
195
|
+
runs stays small enough to pass around (e.g. a cross-project status table).
|
|
196
|
+
"""
|
|
193
197
|
notes = self.notes
|
|
194
198
|
try:
|
|
195
199
|
rel = self.run_dir.resolve().relative_to(root.resolve()).as_posix()
|
|
196
200
|
except ValueError:
|
|
197
201
|
rel = str(self.run_dir)
|
|
198
|
-
|
|
202
|
+
summary = {
|
|
199
203
|
"id": self.id,
|
|
200
204
|
"status": self.status.value,
|
|
201
205
|
"label": self.label,
|
|
@@ -222,6 +226,10 @@ class Manifest:
|
|
|
222
226
|
**self.timing(),
|
|
223
227
|
"progress": self.progress,
|
|
224
228
|
}
|
|
229
|
+
if brief:
|
|
230
|
+
for key in ("params", "why", "last_note"):
|
|
231
|
+
del summary[key]
|
|
232
|
+
return summary
|
|
225
233
|
|
|
226
234
|
@property
|
|
227
235
|
def progress(self) -> dict[str, Any] | None:
|
|
@@ -64,11 +64,9 @@ def project_context(project: Project, *, log_lines: int = 20) -> dict[str, Any]:
|
|
|
64
64
|
expires = parse_iso(str(m.run.get("expires_at", "")))
|
|
65
65
|
if expires is not None and expires <= now:
|
|
66
66
|
expired += 1
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
reverse=True,
|
|
71
|
-
)[:5]
|
|
67
|
+
by_created = sorted(runs, key=lambda m: m.created_at, reverse=True)
|
|
68
|
+
recent = [m for m in by_created if m.status != RunState.ARCHIVED][:5]
|
|
69
|
+
active = [m for m in by_created if m.status in ACTIVE_STATES]
|
|
72
70
|
log_path = project.root / "LOG.md"
|
|
73
71
|
log_tail = ""
|
|
74
72
|
if log_path.is_file():
|
|
@@ -88,7 +86,9 @@ def project_context(project: Project, *, log_lines: int = 20) -> dict[str, Any]:
|
|
|
88
86
|
for name, sim in project.config.simulators.items()
|
|
89
87
|
},
|
|
90
88
|
"status_counts": counts,
|
|
91
|
-
"active_runs":
|
|
89
|
+
"active_runs": len(active),
|
|
90
|
+
# Every submitted / running run, briefly (no params), so one call shows them all.
|
|
91
|
+
"active": [m.to_summary(project.root, brief=True) for m in active],
|
|
92
92
|
"expired_scratch": expired,
|
|
93
93
|
"recent_runs": [m.to_summary(project.root) for m in recent],
|
|
94
94
|
"notes_files": note_files,
|
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import json
|
|
14
14
|
from datetime import datetime, timedelta
|
|
15
|
+
from itertools import pairwise
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
from typing import Any
|
|
17
18
|
|
|
@@ -24,6 +25,7 @@ from runops.util import parse_iso
|
|
|
24
25
|
|
|
25
26
|
SAMPLES_KEPT = 1000 # per attempt; older samples are dropped when the file doubles
|
|
26
27
|
RECENT_WINDOW = timedelta(minutes=10)
|
|
28
|
+
STALL_WINDOW = timedelta(hours=1) # no change across this much observed time -> stalled
|
|
27
29
|
_START_MARK = "runops: start "
|
|
28
30
|
|
|
29
31
|
|
|
@@ -106,11 +108,23 @@ def summarize(
|
|
|
106
108
|
reference = earlier[-1] if earlier else (points[0] if len(points) > 1 else origin)
|
|
107
109
|
rate_attempt = _rate(origin, points[-1])
|
|
108
110
|
rate_recent = _rate(reference, points[-1]) or rate_attempt
|
|
111
|
+
# When done last moved: the first sample, then every sample that exceeds the one before.
|
|
112
|
+
last_change_at = points[0][0]
|
|
113
|
+
for (_, prev_done), (at, now_done) in pairwise(points):
|
|
114
|
+
if now_done > prev_done:
|
|
115
|
+
last_change_at = at
|
|
116
|
+
stalled = last_at - last_change_at
|
|
109
117
|
summary: dict[str, Any] = {
|
|
110
118
|
"done": raw_done,
|
|
111
119
|
"total": total,
|
|
112
120
|
"unit": unit,
|
|
113
121
|
"at": last_at.isoformat(timespec="seconds"),
|
|
122
|
+
"last_change_at": last_change_at.isoformat(timespec="seconds"),
|
|
123
|
+
"stalled_hours": (
|
|
124
|
+
round(stalled.total_seconds() / 3600, 1)
|
|
125
|
+
if not final and stalled >= STALL_WINDOW
|
|
126
|
+
else None
|
|
127
|
+
),
|
|
114
128
|
"fraction": round(done / total, 4) if total else None,
|
|
115
129
|
"rate_recent_per_hour": _round(rate_recent),
|
|
116
130
|
"rate_attempt_per_hour": _round(rate_attempt),
|
|
@@ -157,6 +171,8 @@ def format_progress(progress: dict[str, Any] | None) -> str:
|
|
|
157
171
|
parts.append(f"{3600 / rate:.3g} s/{progress['unit']}")
|
|
158
172
|
if progress.get("eta_hours") is not None:
|
|
159
173
|
parts.append(f"eta {progress['eta_hours']:.1f}h")
|
|
174
|
+
if progress.get("stalled_hours") is not None:
|
|
175
|
+
parts.append(f"stalled {progress['stalled_hours']:.1f}h")
|
|
160
176
|
return " ".join(parts)
|
|
161
177
|
|
|
162
178
|
|
|
@@ -12,6 +12,10 @@ description: Check the progress or result of runops runs (sync, list, log, retry
|
|
|
12
12
|
3. `runo status RUN` shows one run, with progress, recent speed and time left when the
|
|
13
13
|
adapter reports progress (`--json` → `progress`; counted from the resume point, ending
|
|
14
14
|
at the finish). Use it for speed comparisons instead of timing logs by hand.
|
|
15
|
+
`stalled 6.7h` means the progress has not moved across the last syncs for that long
|
|
16
|
+
while Slurm still says RUNNING: check `runo log RUN` and the output mtimes before
|
|
17
|
+
deciding to cancel. For all active runs at once use `runo context --json` (`active`)
|
|
18
|
+
or `runo list --status running --status submitted --brief --json`.
|
|
15
19
|
`runo log RUN` and `runo log RUN --err` show the
|
|
16
20
|
tail of the latest job's stdout / stderr (`-n 200` for more, `-f` to follow).
|
|
17
21
|
4. For a failed run, read the stderr log and the adapter summary
|
|
@@ -11,7 +11,7 @@ description: Full runops (runo) command reference, project layout and manifest f
|
|
|
11
11
|
| `runo doctor [--json]` | check config, adapters, executables, sbatch, skill freshness |
|
|
12
12
|
| `runo update [--skills-only] [-y]` | upgrade simulator packages and redeploy skills / AGENTS.md block |
|
|
13
13
|
| `runo build SIM [--force]` | build a local_source simulator into .runops/build/<sim>-<commit>-<hash> |
|
|
14
|
-
| `runo context [--json]` | project summary
|
|
14
|
+
| `runo context [--json]` | project summary: all active runs with progress, recent runs, group analysis topics |
|
|
15
15
|
| `runo case new NAME [--simulator SIM] [--from CASE]` / `runo case list` | scaffold / list cases |
|
|
16
16
|
| `runo new CASE [--why] [--label] [--set k=v]... [--job k=v]... [--tag] [--scratch] [--dest DIR] [--submit] [--json]` | create a run; `--job` sets this run's `[job]` values |
|
|
17
17
|
| `runo new --from RUN [--why] [--set k=v]... [--job k=v]... [--job-from-case] [--continue] [--submit]` | derive a run with the source's params and `[job]` |
|
|
@@ -20,10 +20,10 @@ description: Full runops (runo) command reference, project layout and manifest f
|
|
|
20
20
|
| `runo submit --all [DIR] [-y]` | sbatch every created run under DIR |
|
|
21
21
|
| `runo cancel RUN... [--why TEXT] [-y]` | scancel; `--why` is noted and `retry` asks before reviving |
|
|
22
22
|
| `runo retry RUN [--submit] [-y]` | failed/cancelled -> created |
|
|
23
|
-
| `runo status [RUN...] [--json]` | show manifest state (no Slurm call) |
|
|
23
|
+
| `runo status [RUN...] [--brief] [--json]` | show manifest state (no Slurm call) |
|
|
24
24
|
| `runo sync [RUN...] [--json]` | update state from Slurm |
|
|
25
25
|
| `runo log RUN [--err] [-n N] [-f]` | latest job stdout/stderr |
|
|
26
|
-
| `runo list [PATH] [--all] [--since 30d] [--case C] [--sweep L] [--status S] [--tag T] [--scratch] [--archived] [--json]` | runs |
|
|
26
|
+
| `runo list [PATH] [--all] [--since 30d] [--case C] [--sweep L] [--status S] [--tag T] [--scratch] [--archived] [--brief] [--json]` | runs (`--brief`: no params/why/notes) |
|
|
27
27
|
| `runo note RUN [TEXT] [--by WHO]` | append / show notes |
|
|
28
28
|
| `runo pin RUN [--off] [--reason TEXT]` | keep a run visible and safe from tidy/delete |
|
|
29
29
|
| `runo summarize RUN... [--force]` | analysis/summary.json from the adapter |
|
|
@@ -118,3 +118,53 @@ def test_runs_without_progress_hook_are_unchanged(project_dir: Path, cli, fake_s
|
|
|
118
118
|
m = Project.open(project_dir).resolve_run(run_id)
|
|
119
119
|
assert "progress" not in m.job["attempts"][-1]
|
|
120
120
|
assert not list((m.run_dir / "submit").glob("progress.*"))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_summarize_flags_a_run_whose_progress_stopped_moving() -> None:
|
|
124
|
+
samples = [
|
|
125
|
+
{"at": _at(0), "done": 100},
|
|
126
|
+
{"at": _at(30), "done": 200},
|
|
127
|
+
{"at": _at(60), "done": 200},
|
|
128
|
+
{"at": _at(120), "done": 200}, # 90 min without a change
|
|
129
|
+
]
|
|
130
|
+
out = summarize(samples, total=1000, unit="batch", start=0, started_at=None)
|
|
131
|
+
assert out["last_change_at"] == _at(30)
|
|
132
|
+
assert out["stalled_hours"] == 1.5
|
|
133
|
+
assert format_progress(out).endswith("stalled 1.5h")
|
|
134
|
+
|
|
135
|
+
moving = summarize(samples[:3], total=1000, unit="batch", start=0, started_at=None)
|
|
136
|
+
assert "stalled_hours" not in moving # 30 min without a change is not yet a stall
|
|
137
|
+
final = summarize(samples, total=1000, unit="batch", start=0, started_at=None, final=True)
|
|
138
|
+
assert "stalled_hours" not in final # a finished run is not stalled
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_brief_listing_and_context_active(project_dir: Path, cli, fake_slurm) -> None: # type: ignore[no-untyped-def]
|
|
142
|
+
_project_with_progress(project_dir)
|
|
143
|
+
(run_id,) = cli("new", "prog", "--submit", "--why", "p").run_ids()
|
|
144
|
+
cli("new", "demo", "--why", "a draft")
|
|
145
|
+
job = fake_slurm.last_job
|
|
146
|
+
m = Project.open(project_dir).resolve_run(run_id)
|
|
147
|
+
(m.run_dir / "work/progress.txt").write_text("2500 10000 0\n")
|
|
148
|
+
fake_slurm.states[job] = "RUNNING"
|
|
149
|
+
assert cli("sync").code == 0
|
|
150
|
+
|
|
151
|
+
full = json.loads(cli("list", "--json").out)
|
|
152
|
+
brief = json.loads(cli("list", "--brief", "--json").out)
|
|
153
|
+
assert {r["id"] for r in full} == {r["id"] for r in brief}
|
|
154
|
+
assert "params" in full[0] and "why" in full[0]
|
|
155
|
+
assert not {"params", "why", "last_note"} & set(brief[0])
|
|
156
|
+
assert brief[0]["progress"] is None or "done" in brief[0]["progress"]
|
|
157
|
+
table = cli("list", "--brief").out
|
|
158
|
+
assert "why" not in table.splitlines()[0] and run_id in table
|
|
159
|
+
|
|
160
|
+
status = json.loads(cli("status", "--brief", "--json").out)
|
|
161
|
+
assert [s["id"] for s in status] == [run_id] and "notes" not in status[0]
|
|
162
|
+
|
|
163
|
+
data = json.loads(cli("context", "--json").out)
|
|
164
|
+
assert data["active_runs"] == 1
|
|
165
|
+
assert [r["id"] for r in data["active"]] == [run_id]
|
|
166
|
+
assert "params" not in data["active"][0]
|
|
167
|
+
assert data["active"][0]["progress"]["done"] == 2500
|
|
168
|
+
assert len(data["recent_runs"]) == 2
|
|
169
|
+
text = cli("context").out
|
|
170
|
+
assert "active:" in text and "25%" in text
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/examples/refactor-request.md
RENAMED
|
File without changes
|
{runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/references/anti-patterns.md
RENAMED
|
File without changes
|
{runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/references/api-compatibility.md
RENAMED
|
File without changes
|
|
File without changes
|
{runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/references/refactor-playbook.md
RENAMED
|
File without changes
|
{runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/references/verification.md
RENAMED
|
File without changes
|
{runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/scripts/api_surface_snapshot.py
RENAMED
|
File without changes
|
{runops-2.6.0 → runops-2.7.0}/.agents/skills/python-package-refactor/scripts/import_smoke.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/runs/plane/R20260701-0001--a/manifest.toml
RENAMED
|
File without changes
|
{runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/runs/plane/R20260702-0001/manifest.toml
RENAMED
|
File without changes
|
{runops-2.6.0 → runops-2.7.0}/tests/fixtures/v1-project/runs/scan/R20260701-0002/manifest.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|