runops 0.2.1__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. {runops-0.2.1 → runops-0.3.0}/.claude/rules/commands.md +2 -2
  2. {runops-0.2.1 → runops-0.3.0}/.claude/rules/dev-workflow.md +9 -0
  3. {runops-0.2.1 → runops-0.3.0}/.claude/rules/gotchas.md +11 -0
  4. {runops-0.2.1 → runops-0.3.0}/.claude/settings.json +7 -0
  5. runops-0.3.0/.claude/skills/release/SKILL.md +104 -0
  6. runops-0.3.0/.claude/skills/triage/SKILL.md +106 -0
  7. runops-0.3.0/.claude/skills/update-docs/SKILL.md +73 -0
  8. {runops-0.2.1 → runops-0.3.0}/CLAUDE.md +2 -2
  9. {runops-0.2.1 → runops-0.3.0}/PKG-INFO +1 -1
  10. {runops-0.2.1 → runops-0.3.0}/docs/agent-user-guide.md +1 -1
  11. {runops-0.2.1 → runops-0.3.0}/docs/toml-reference.md +2 -0
  12. {runops-0.2.1 → runops-0.3.0}/pyproject.toml +1 -1
  13. {runops-0.2.1 → runops-0.3.0}/src/runops/__init__.py +1 -1
  14. {runops-0.2.1 → runops-0.3.0}/src/runops/adapters/contrib/beach.py +1 -1
  15. {runops-0.2.1 → runops-0.3.0}/src/runops/adapters/contrib/emses.py +6 -2
  16. {runops-0.2.1 → runops-0.3.0}/src/runops/adapters/generic.py +4 -2
  17. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/knowledge.py +2 -2
  18. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/status.py +35 -0
  19. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/submit.py +27 -4
  20. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/update_harness.py +20 -2
  21. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/update_refs.py +1 -1
  22. {runops-0.2.1 → runops-0.3.0}/src/runops/core/actions.py +6 -3
  23. {runops-0.2.1 → runops-0.3.0}/src/runops/core/case.py +3 -0
  24. {runops-0.2.1 → runops-0.3.0}/src/runops/core/knowledge.py +1 -1
  25. {runops-0.2.1 → runops-0.3.0}/src/runops/core/run_creation.py +2 -0
  26. {runops-0.2.1 → runops-0.3.0}/src/runops/jobgen/generator.py +4 -0
  27. {runops-0.2.1 → runops-0.3.0}/src/runops/sites/camphor.md +19 -0
  28. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/agent.md +2 -0
  29. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/scaffold/rules/runops-workflow.md +2 -1
  30. runops-0.3.0/src/runops/templates/skills/feedback/SKILL.md +116 -0
  31. runops-0.3.0/src/runops/templates/skills/release/SKILL.md +107 -0
  32. {runops-0.2.1 → runops-0.3.0}/.claude/agents/beach.md +0 -0
  33. {runops-0.2.1 → runops-0.3.0}/.claude/agents/emses.md +0 -0
  34. {runops-0.2.1 → runops-0.3.0}/.claude/agents/implement-adapter.md +0 -0
  35. {runops-0.2.1 → runops-0.3.0}/.claude/agents/implement-cli.md +0 -0
  36. {runops-0.2.1 → runops-0.3.0}/.claude/agents/implement-core.md +0 -0
  37. {runops-0.2.1 → runops-0.3.0}/.claude/agents/implement-launcher.md +0 -0
  38. {runops-0.2.1 → runops-0.3.0}/.claude/agents/implement-slurm.md +0 -0
  39. {runops-0.2.1 → runops-0.3.0}/.claude/agents/scaffold.md +0 -0
  40. {runops-0.2.1 → runops-0.3.0}/.claude/agents/spec-reviewer.md +0 -0
  41. {runops-0.2.1 → runops-0.3.0}/.claude/agents/test-writer.md +0 -0
  42. {runops-0.2.1 → runops-0.3.0}/.claude/rules/architecture.md +0 -0
  43. {runops-0.2.1 → runops-0.3.0}/.claude/rules/knowledge-layer.md +0 -0
  44. {runops-0.2.1 → runops-0.3.0}/.claude/skills/check/SKILL.md +0 -0
  45. {runops-0.2.1 → runops-0.3.0}/.claude/skills/improve-harness/SKILL.md +0 -0
  46. {runops-0.2.1 → runops-0.3.0}/.claude/skills/test-module/SKILL.md +0 -0
  47. {runops-0.2.1 → runops-0.3.0}/.dockerignore +0 -0
  48. {runops-0.2.1 → runops-0.3.0}/.gitattributes +0 -0
  49. {runops-0.2.1 → runops-0.3.0}/.github/workflows/ci.yml +0 -0
  50. {runops-0.2.1 → runops-0.3.0}/.github/workflows/publish.yml +0 -0
  51. {runops-0.2.1 → runops-0.3.0}/.gitignore +0 -0
  52. {runops-0.2.1 → runops-0.3.0}/.pre-commit-config.yaml +0 -0
  53. {runops-0.2.1 → runops-0.3.0}/AGENTS.md +0 -0
  54. {runops-0.2.1 → runops-0.3.0}/Dockerfile.diagrams +0 -0
  55. {runops-0.2.1 → runops-0.3.0}/LICENSE +0 -0
  56. {runops-0.2.1 → runops-0.3.0}/README.md +0 -0
  57. {runops-0.2.1 → runops-0.3.0}/SKILLS.md +0 -0
  58. {runops-0.2.1 → runops-0.3.0}/SPEC.md +0 -0
  59. {runops-0.2.1 → runops-0.3.0}/docs/architecture.md +0 -0
  60. {runops-0.2.1 → runops-0.3.0}/docs/extending.md +0 -0
  61. {runops-0.2.1 → runops-0.3.0}/docs/figures/agent-project-flow/human-gates.png +0 -0
  62. {runops-0.2.1 → runops-0.3.0}/docs/figures/agent-project-flow/init-world.png +0 -0
  63. {runops-0.2.1 → runops-0.3.0}/docs/figures/agent-project-flow/operation-loop.png +0 -0
  64. {runops-0.2.1 → runops-0.3.0}/docs/figures/src-structure/overview.png +0 -0
  65. {runops-0.2.1 → runops-0.3.0}/docs/figures/src-structure/run-creation-resolution.png +0 -0
  66. {runops-0.2.1 → runops-0.3.0}/docs/figures/src-structure/site-resolution.png +0 -0
  67. {runops-0.2.1 → runops-0.3.0}/docs/get-started-with-agent.md +0 -0
  68. {runops-0.2.1 → runops-0.3.0}/docs/knowledge-layer.md +0 -0
  69. {runops-0.2.1 → runops-0.3.0}/docs/project-flow.md +0 -0
  70. {runops-0.2.1 → runops-0.3.0}/docs/simulator-kb-spec.md +0 -0
  71. {runops-0.2.1 → runops-0.3.0}/docs/src-structure.md +0 -0
  72. {runops-0.2.1 → runops-0.3.0}/entrypoints.toml +0 -0
  73. {runops-0.2.1 → runops-0.3.0}/examples/beach/campaign.toml +0 -0
  74. {runops-0.2.1 → runops-0.3.0}/examples/beach/cases/periodic2_basic/beach_template.toml +0 -0
  75. {runops-0.2.1 → runops-0.3.0}/examples/beach/cases/periodic2_basic/case.toml +0 -0
  76. {runops-0.2.1 → runops-0.3.0}/examples/beach/launchers.toml +0 -0
  77. {runops-0.2.1 → runops-0.3.0}/examples/beach/simulators.toml +0 -0
  78. {runops-0.2.1 → runops-0.3.0}/examples/beach/surveys/density_scan/survey.toml +0 -0
  79. {runops-0.2.1 → runops-0.3.0}/examples/emses/campaign.toml +0 -0
  80. {runops-0.2.1 → runops-0.3.0}/examples/emses/cases/flat_surface/case.toml +0 -0
  81. {runops-0.2.1 → runops-0.3.0}/examples/emses/cases/flat_surface/plasma.toml +0 -0
  82. {runops-0.2.1 → runops-0.3.0}/examples/emses/launchers.toml +0 -0
  83. {runops-0.2.1 → runops-0.3.0}/examples/emses/simulators.toml +0 -0
  84. {runops-0.2.1 → runops-0.3.0}/examples/emses/surveys/mag_angle_scan/survey.toml +0 -0
  85. {runops-0.2.1 → runops-0.3.0}/schemas/campaign.json +0 -0
  86. {runops-0.2.1 → runops-0.3.0}/schemas/case.json +0 -0
  87. {runops-0.2.1 → runops-0.3.0}/schemas/launchers.json +0 -0
  88. {runops-0.2.1 → runops-0.3.0}/schemas/manifest.json +0 -0
  89. {runops-0.2.1 → runops-0.3.0}/schemas/runops.json +0 -0
  90. {runops-0.2.1 → runops-0.3.0}/schemas/simulators.json +0 -0
  91. {runops-0.2.1 → runops-0.3.0}/schemas/survey.json +0 -0
  92. {runops-0.2.1 → runops-0.3.0}/scripts/diagram_utils.py +0 -0
  93. {runops-0.2.1 → runops-0.3.0}/scripts/generate_agent_project_flow.py +0 -0
  94. {runops-0.2.1 → runops-0.3.0}/scripts/generate_architecture_diagrams.py +0 -0
  95. {runops-0.2.1 → runops-0.3.0}/scripts/render_diagrams_in_docker.py +0 -0
  96. {runops-0.2.1 → runops-0.3.0}/src/runops/adapters/__init__.py +0 -0
  97. {runops-0.2.1 → runops-0.3.0}/src/runops/adapters/_utils/__init__.py +0 -0
  98. {runops-0.2.1 → runops-0.3.0}/src/runops/adapters/_utils/toml_utils.py +0 -0
  99. {runops-0.2.1 → runops-0.3.0}/src/runops/adapters/base.py +0 -0
  100. {runops-0.2.1 → runops-0.3.0}/src/runops/adapters/contrib/__init__.py +0 -0
  101. {runops-0.2.1 → runops-0.3.0}/src/runops/adapters/registry.py +0 -0
  102. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/__init__.py +0 -0
  103. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/analyze.py +0 -0
  104. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/clone.py +0 -0
  105. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/config.py +0 -0
  106. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/context.py +0 -0
  107. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/create.py +0 -0
  108. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/dashboard.py +0 -0
  109. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/extend.py +0 -0
  110. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/history.py +0 -0
  111. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/init.py +0 -0
  112. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/jobs.py +0 -0
  113. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/list.py +0 -0
  114. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/log.py +0 -0
  115. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/main.py +0 -0
  116. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/manage.py +0 -0
  117. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/new.py +0 -0
  118. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/notes.py +0 -0
  119. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/run_lookup.py +0 -0
  120. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/setup.py +0 -0
  121. {runops-0.2.1 → runops-0.3.0}/src/runops/cli/update.py +0 -0
  122. {runops-0.2.1 → runops-0.3.0}/src/runops/core/__init__.py +0 -0
  123. {runops-0.2.1 → runops-0.3.0}/src/runops/core/analysis.py +0 -0
  124. {runops-0.2.1 → runops-0.3.0}/src/runops/core/campaign.py +0 -0
  125. {runops-0.2.1 → runops-0.3.0}/src/runops/core/context.py +0 -0
  126. {runops-0.2.1 → runops-0.3.0}/src/runops/core/discovery.py +0 -0
  127. {runops-0.2.1 → runops-0.3.0}/src/runops/core/environment.py +0 -0
  128. {runops-0.2.1 → runops-0.3.0}/src/runops/core/exceptions.py +0 -0
  129. {runops-0.2.1 → runops-0.3.0}/src/runops/core/knowledge_source.py +0 -0
  130. {runops-0.2.1 → runops-0.3.0}/src/runops/core/manifest.py +0 -0
  131. {runops-0.2.1 → runops-0.3.0}/src/runops/core/project.py +0 -0
  132. {runops-0.2.1 → runops-0.3.0}/src/runops/core/provenance.py +0 -0
  133. {runops-0.2.1 → runops-0.3.0}/src/runops/core/retry.py +0 -0
  134. {runops-0.2.1 → runops-0.3.0}/src/runops/core/run.py +0 -0
  135. {runops-0.2.1 → runops-0.3.0}/src/runops/core/site.py +0 -0
  136. {runops-0.2.1 → runops-0.3.0}/src/runops/core/state.py +0 -0
  137. {runops-0.2.1 → runops-0.3.0}/src/runops/core/survey.py +0 -0
  138. {runops-0.2.1 → runops-0.3.0}/src/runops/core/validation.py +0 -0
  139. {runops-0.2.1 → runops-0.3.0}/src/runops/harness/__init__.py +0 -0
  140. {runops-0.2.1 → runops-0.3.0}/src/runops/harness/builder.py +0 -0
  141. {runops-0.2.1 → runops-0.3.0}/src/runops/harness/claude.py +0 -0
  142. {runops-0.2.1 → runops-0.3.0}/src/runops/jobgen/__init__.py +0 -0
  143. {runops-0.2.1 → runops-0.3.0}/src/runops/launchers/__init__.py +0 -0
  144. {runops-0.2.1 → runops-0.3.0}/src/runops/launchers/base.py +0 -0
  145. {runops-0.2.1 → runops-0.3.0}/src/runops/launchers/mpiexec.py +0 -0
  146. {runops-0.2.1 → runops-0.3.0}/src/runops/launchers/mpirun.py +0 -0
  147. {runops-0.2.1 → runops-0.3.0}/src/runops/launchers/srun.py +0 -0
  148. {runops-0.2.1 → runops-0.3.0}/src/runops/sites/__init__.py +0 -0
  149. {runops-0.2.1 → runops-0.3.0}/src/runops/sites/camphor.toml +0 -0
  150. {runops-0.2.1 → runops-0.3.0}/src/runops/slurm/__init__.py +0 -0
  151. {runops-0.2.1 → runops-0.3.0}/src/runops/slurm/query.py +0 -0
  152. {runops-0.2.1 → runops-0.3.0}/src/runops/slurm/submit.py +0 -0
  153. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/__init__.py +0 -0
  154. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/adapters/beach/agent_guide.md +0 -0
  155. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/adapters/beach/beach.toml +0 -0
  156. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/adapters/beach/case.toml +0 -0
  157. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/adapters/beach/summarize.py +0 -0
  158. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/adapters/emses/agent_guide.md +0 -0
  159. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/adapters/emses/case.toml +0 -0
  160. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/adapters/emses/plasma.toml +0 -0
  161. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/adapters/emses/summarize.py +0 -0
  162. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/adapters/generic/case.toml.j2 +0 -0
  163. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/adapters/generic/summarize.py +0 -0
  164. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/rules/cookbook.md +0 -0
  165. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/scaffold/campaign.toml.j2 +0 -0
  166. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/scaffold/cases_claude.md +0 -0
  167. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/scaffold/facts.toml +0 -0
  168. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/scaffold/gitignore.txt +0 -0
  169. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/scaffold/notes/README.md +0 -0
  170. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/scaffold/rules/plan-before-act.md +0 -0
  171. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/scaffold/rules/upstream-feedback.md +0 -0
  172. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/scaffold/runs_claude.md +0 -0
  173. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/scaffold/vscode_settings.json +0 -0
  174. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/skills/analyze/SKILL.md +0 -0
  175. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/skills/check-status/SKILL.md +0 -0
  176. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/skills/cleanup/SKILL.md +0 -0
  177. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/skills/create-run/SKILL.md +0 -0
  178. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/skills/debug-failed/SKILL.md +0 -0
  179. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/skills/learn/SKILL.md +0 -0
  180. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/skills/new-case/SKILL.md +0 -0
  181. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/skills/note/SKILL.md +0 -0
  182. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/skills/run-all/SKILL.md +0 -0
  183. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/skills/runops-reference/SKILL.md +0 -0
  184. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/skills/setup-campaign/SKILL.md +0 -0
  185. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/skills/setup-env/SKILL.md +0 -0
  186. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/skills/survey-design/SKILL.md +0 -0
  187. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/skills/update-runops/SKILL.md +0 -0
  188. {runops-0.2.1 → runops-0.3.0}/src/runops/templates/survey.toml.j2 +0 -0
  189. {runops-0.2.1 → runops-0.3.0}/tests/__init__.py +0 -0
  190. {runops-0.2.1 → runops-0.3.0}/tests/conftest.py +0 -0
  191. {runops-0.2.1 → runops-0.3.0}/tests/fixtures/sample_case.toml +0 -0
  192. {runops-0.2.1 → runops-0.3.0}/tests/fixtures/sample_launchers.toml +0 -0
  193. {runops-0.2.1 → runops-0.3.0}/tests/fixtures/sample_manifest.toml +0 -0
  194. {runops-0.2.1 → runops-0.3.0}/tests/fixtures/sample_runops.toml +0 -0
  195. {runops-0.2.1 → runops-0.3.0}/tests/fixtures/sample_simulators.toml +0 -0
  196. {runops-0.2.1 → runops-0.3.0}/tests/fixtures/sample_survey.toml +0 -0
  197. {runops-0.2.1 → runops-0.3.0}/tests/test_adapters/__init__.py +0 -0
  198. {runops-0.2.1 → runops-0.3.0}/tests/test_adapters/test_beach.py +0 -0
  199. {runops-0.2.1 → runops-0.3.0}/tests/test_adapters/test_emses.py +0 -0
  200. {runops-0.2.1 → runops-0.3.0}/tests/test_adapters/test_generic.py +0 -0
  201. {runops-0.2.1 → runops-0.3.0}/tests/test_adapters/test_registry.py +0 -0
  202. {runops-0.2.1 → runops-0.3.0}/tests/test_adapters/test_toml_utils.py +0 -0
  203. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/__init__.py +0 -0
  204. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_analyze.py +0 -0
  205. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_clone.py +0 -0
  206. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_config.py +0 -0
  207. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_context_cli.py +0 -0
  208. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_create.py +0 -0
  209. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_dashboard.py +0 -0
  210. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_extend.py +0 -0
  211. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_history.py +0 -0
  212. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_init.py +0 -0
  213. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_jobs.py +0 -0
  214. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_knowledge.py +0 -0
  215. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_list.py +0 -0
  216. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_log.py +0 -0
  217. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_main.py +0 -0
  218. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_manage.py +0 -0
  219. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_new.py +0 -0
  220. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_notes.py +0 -0
  221. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_setup.py +0 -0
  222. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_status.py +0 -0
  223. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_submit.py +0 -0
  224. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_update.py +0 -0
  225. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_update_harness.py +0 -0
  226. {runops-0.2.1 → runops-0.3.0}/tests/test_cli/test_update_refs.py +0 -0
  227. {runops-0.2.1 → runops-0.3.0}/tests/test_core/__init__.py +0 -0
  228. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_action_contract.py +0 -0
  229. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_actions.py +0 -0
  230. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_analysis.py +0 -0
  231. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_case.py +0 -0
  232. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_context.py +0 -0
  233. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_discovery.py +0 -0
  234. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_knowledge_source.py +0 -0
  235. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_manifest.py +0 -0
  236. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_project.py +0 -0
  237. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_provenance.py +0 -0
  238. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_retry.py +0 -0
  239. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_run.py +0 -0
  240. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_run_creation.py +0 -0
  241. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_site.py +0 -0
  242. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_state.py +0 -0
  243. {runops-0.2.1 → runops-0.3.0}/tests/test_core/test_survey.py +0 -0
  244. {runops-0.2.1 → runops-0.3.0}/tests/test_e2e/test_bootstrap.py +0 -0
  245. {runops-0.2.1 → runops-0.3.0}/tests/test_harness/test_claude.py +0 -0
  246. {runops-0.2.1 → runops-0.3.0}/tests/test_launchers/__init__.py +0 -0
  247. {runops-0.2.1 → runops-0.3.0}/tests/test_launchers/test_launcher_base.py +0 -0
  248. {runops-0.2.1 → runops-0.3.0}/tests/test_launchers/test_mpiexec.py +0 -0
  249. {runops-0.2.1 → runops-0.3.0}/tests/test_launchers/test_mpirun.py +0 -0
  250. {runops-0.2.1 → runops-0.3.0}/tests/test_launchers/test_srun.py +0 -0
  251. {runops-0.2.1 → runops-0.3.0}/tests/test_slurm/__init__.py +0 -0
  252. {runops-0.2.1 → runops-0.3.0}/tests/test_slurm/test_jobgen.py +0 -0
  253. {runops-0.2.1 → runops-0.3.0}/tests/test_slurm/test_slurm_query.py +0 -0
  254. {runops-0.2.1 → runops-0.3.0}/tests/test_slurm/test_slurm_submit.py +0 -0
  255. {runops-0.2.1 → runops-0.3.0}/uv.lock +0 -0
@@ -23,7 +23,7 @@
23
23
  | `runops case new CASE [--minimal] [--survey]` | case のスキャフォールド生成 |
24
24
  | `runops runs create CASE` | case から単一 run を生成 |
25
25
  | `runops runs sweep [DIR] [--dry-run]` | survey.toml からパラメータ直積で全 run 生成 |
26
- | `runops runs submit [RUN]` | run を sbatch で投入 (`-qn`, `--afterok` 対応) |
26
+ | `runops runs submit [RUN]` | run を sbatch で投入 (`-qn`, `--qos`, `--afterok` 対応) |
27
27
  | `runops runs submit --all [DIR]` | created な run を一括投入 |
28
28
  | `runops runs clone` | run 複製・派生 |
29
29
  | `runops runs extend` | スナップショットから継続 run 生成 |
@@ -33,7 +33,7 @@
33
33
  | コマンド | 説明 |
34
34
  |---------|------|
35
35
  | `runops runs status [RUNS...]` | run 状態確認 (複数指定可) |
36
- | `runops runs sync [RUNS...]` | Slurm 状態を manifest に反映 |
36
+ | `runops runs sync [RUNS...]` | Slurm 状態を manifest に反映 (複数 run 時はサマリ表示) |
37
37
  | `runops runs log [RUN]` | 最新 job の stdout/stderr 表示 + 進捗% |
38
38
  | `runops runs jobs [PATH] [--watch SECS]` | 実行中ジョブ一覧 |
39
39
  | `runops runs dashboard [TARGETS...] [--watch SECS]` | 複数 run の進捗表 |
@@ -37,3 +37,12 @@ CI でも同じチェックが走る。ruff format 違反は自動修正可 (`uv
37
37
  - commit message は英語推奨 (`fix:`, `feat:`, `refactor:`, `test:`, `docs:`)
38
38
  - `--no-verify` / `--force` は使わない
39
39
  - PR は main ブランチへ
40
+
41
+ ## リリース
42
+
43
+ `/release` スキルで実施する。手順の概要:
44
+
45
+ 1. 品質ゲート通過を確認
46
+ 2. `pyproject.toml` と `src/runops/__init__.py` のバージョンを **同時に** 更新
47
+ 3. `git commit -m "chore: bump version to X.Y.Z"` + `git tag vX.Y.Z`
48
+ 4. `git push origin main --tags` で CI が PyPI にパブリッシュ
@@ -35,6 +35,17 @@ local が team を **マージ上書き** する。同じキーを両方に書
35
35
  Adapter が返すリストは **累積** (重複排除) される。
36
36
  同じ package を複数 adapter が返しても 1 回だけ install される。
37
37
 
38
+ ## バージョン二重管理
39
+
40
+ `pyproject.toml` の `version` と `src/runops/__init__.py` の `__version__` は
41
+ **必ず同時に更新する**。片方だけ変えるとランタイム (`runops --version`) と
42
+ PyPI パッケージのバージョンがずれる。`/release` スキルを使えば自動で両方更新される。
43
+
44
+ ## read_text() の encoding
45
+
46
+ `Path.read_text()` は `encoding` 引数を省略するとシステムロケール依存になる。
47
+ 日本語を含むファイルを読むときは必ず `encoding="utf-8"` を明示する。
48
+
38
49
  ## ハーネス lock
39
50
 
40
51
  `harness.lock` は **テンプレートの hash** を記録する。
@@ -13,7 +13,14 @@
13
13
  "Bash(git branch*)",
14
14
  "Bash(git checkout*)",
15
15
  "Bash(git stash*)",
16
+ "Bash(git add *)",
17
+ "Bash(git tag *)",
18
+ "Bash(gh issue *)",
19
+ "Bash(gh pr *)",
20
+ "Bash(gh run *)",
21
+ "Bash(mkdir *)",
16
22
  "Bash(python -c *)",
23
+ "Bash(uv run python *)",
17
24
  "Bash(wc -l *)",
18
25
  "Bash(ls *)",
19
26
  "Edit(src/**)",
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: release
3
+ description: "Prepare and publish a new runops release. Bump version, sync __init__.py, generate changelog, create commit and tag, and push to trigger PyPI publish."
4
+ ---
5
+
6
+ # runops リリース
7
+
8
+ `/release` は新しいバージョンの runops をリリースするスキル。
9
+
10
+ ## 使い方
11
+
12
+ ```
13
+ /release patch # 0.2.1 -> 0.2.2
14
+ /release minor # 0.2.1 -> 0.3.0
15
+ /release major # 0.2.1 -> 1.0.0
16
+ /release 0.3.0 # 明示的にバージョン指定
17
+ ```
18
+
19
+ 引数なしで呼んだ場合は、変更内容から bump レベルを自動判定し、
20
+ 確認 → リリースまで一気通貫で実行する。
21
+
22
+ ## 手順
23
+
24
+ ### 1. リリース可否を確認する
25
+
26
+ ```bash
27
+ # 未コミットの変更がないか
28
+ git status --porcelain
29
+
30
+ # テストが全て通るか
31
+ uv run pytest tests/ -x -q
32
+
33
+ # lint / type check
34
+ uv run ruff check src/ tests/
35
+ uv run mypy src/
36
+ ```
37
+
38
+ 品質ゲートが通らなければリリースを中止する。
39
+
40
+ ### 2. 変更内容を把握する
41
+
42
+ ```bash
43
+ # 前回リリースタグからの変更
44
+ git log $(git describe --tags --abbrev=0 2>/dev/null || echo HEAD~20)..HEAD --oneline
45
+ ```
46
+
47
+ commit message から以下を分類する:
48
+
49
+ - **Breaking changes** (`feat!:`, `BREAKING CHANGE`) → major bump 候補
50
+ - **New features** (`feat:`) → minor bump 候補
51
+ - **Bug fixes** (`fix:`) → patch bump 候補
52
+ - **Other** (`refactor:`, `test:`, `docs:`, `chore:`)
53
+
54
+ ### 3. バージョンを決定する
55
+
56
+ 引数で指定されていればそれを使う。なければ以下のルールで自動判定する:
57
+
58
+ - `feat!:` or `BREAKING CHANGE` あり → **major**
59
+ - `feat:` あり → **minor**
60
+ - `fix:` のみ → **patch**
61
+ - `docs:` / `chore:` のみ → **patch**
62
+
63
+ ### 4. バージョンを更新する
64
+
65
+ 2 箇所を同時に更新する (**ずれ防止**):
66
+
67
+ 1. `pyproject.toml` の `version = "X.Y.Z"` — pip / PyPI が参照する正本
68
+ 2. `src/runops/__init__.py` の `__version__ = "X.Y.Z"` — ランタイム参照
69
+
70
+ ### 5. コミットとタグ
71
+
72
+ ```bash
73
+ git add pyproject.toml src/runops/__init__.py
74
+ git commit -m "chore: bump version to X.Y.Z"
75
+ git tag vX.Y.Z
76
+ ```
77
+
78
+ ### 6. push してリリース
79
+
80
+ ユーザーの確認を得てから push する:
81
+
82
+ ```bash
83
+ git push origin main
84
+ git push origin vX.Y.Z
85
+ ```
86
+
87
+ `v*` タグの push により `.github/workflows/publish.yml` が起動し、
88
+ CI が自動で PyPI にパブリッシュする。
89
+
90
+ ### 7. 確認
91
+
92
+ ```bash
93
+ gh run list --workflow=publish.yml --limit 1
94
+ ```
95
+
96
+ ## 引数なしの場合
97
+
98
+ 変更内容を分類し、bump レベルを自動判定して、リリースまで実行する。
99
+ 具体的には:
100
+
101
+ 1. 前回タグからのコミットを分類 (breaking / feat / fix / other)
102
+ 2. bump レベルを自動判定
103
+ 3. 変更サマリとバージョンをユーザーに提示して確認
104
+ 4. 確認が取れたら手順 4〜7 を順に実行 (バージョン更新 → コミット → タグ → push)
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: triage
3
+ description: "Triage open GitHub issues: review new issues, assess priority, filter spam/malicious content, suggest which to tackle, and close resolved or invalid issues."
4
+ ---
5
+
6
+ # Issue トリアージ
7
+
8
+ `/triage` は runops リポジトリの GitHub issue を一括レビューし、
9
+ 対応方針を提案するスキル。
10
+
11
+ ## 手順
12
+
13
+ ### 1. Open issue を一覧取得
14
+
15
+ ```bash
16
+ gh issue list --state open --limit 30
17
+ ```
18
+
19
+ ### 2. 各 issue を評価
20
+
21
+ issue ごとに以下の観点で評価する:
22
+
23
+ | 観点 | 判定 |
24
+ |------|------|
25
+ | **正当性** | 悪意・スパム・無関係な内容でないか |
26
+ | **再現性** | バグなら再現手順があるか、実際にコードベースで確認できるか |
27
+ | **影響度** | 影響を受けるユーザー・ワークフローの範囲 |
28
+ | **難易度** | 修正の複雑さ (軽微 / 中 / 大) |
29
+ | **重複** | 既存の issue や実装済み機能と重複していないか |
30
+
31
+ ### 3. 悪意ある issue のフィルタリング
32
+
33
+ 以下に該当する issue は close を提案する:
34
+
35
+ - スパム (無関係な宣伝、bot 投稿)
36
+ - 悪意のあるコード・リンクを含む
37
+ - プロジェクトと無関係な内容
38
+ - 個人攻撃・ハラスメント
39
+
40
+ ```bash
41
+ gh issue close <NUMBER> --comment "Closed: <理由>"
42
+ ```
43
+
44
+ ### 4. 優先度付けと対応方針の提案
45
+
46
+ ユーザーに以下の形式で報告する:
47
+
48
+ ```
49
+ ## トリアージ結果
50
+
51
+ ### 対応推奨 (高)
52
+ - #N: <タイトル> — <理由・工数見積>
53
+
54
+ ### 対応推奨 (中)
55
+ - #N: <タイトル> — <理由・工数見積>
56
+
57
+ ### 保留 / 要議論
58
+ - #N: <タイトル> — <保留理由>
59
+
60
+ ### Close 推奨
61
+ - #N: <タイトル> — <close 理由>
62
+ ```
63
+
64
+ ### 5. ユーザー判断を仰ぐ
65
+
66
+ トリアージ結果を提示した後、ユーザーに以下を確認する:
67
+
68
+ - どの issue に取り掛かるか
69
+ - close してよい issue はどれか
70
+ - 追加情報が必要な issue はあるか
71
+
72
+ ## 対応完了時の処理
73
+
74
+ issue に対応した後は以下の手順で close する:
75
+
76
+ ### コミットでの紐づけ
77
+
78
+ commit message に `Closes #N` を含めると、push 時に自動 close される:
79
+
80
+ ```
81
+ feat: add --qos option to runs submit
82
+
83
+ Closes #15
84
+ ```
85
+
86
+ ### 手動 close
87
+
88
+ push 前に close する場合や、コミットに紐づけない場合:
89
+
90
+ ```bash
91
+ gh issue close <NUMBER> --comment "Fixed in <commit-hash> — <概要>"
92
+ ```
93
+
94
+ ### 対応しないと決めた issue
95
+
96
+ 理由を明記して close する:
97
+
98
+ ```bash
99
+ gh issue close <NUMBER> --comment "Won't fix: <理由>"
100
+ ```
101
+
102
+ ## 注意事項
103
+
104
+ - ユーザーの確認なしに issue を close しない (スパム除く)
105
+ - close 時は必ずコメントで理由を残す
106
+ - 対応済み issue は対応コミットのハッシュを記載する
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: update-docs
3
+ description: "Reflect code changes into documentation. Scan recent commits or staged changes, identify which docs need updating, and apply the updates. Use after implementing features, fixing bugs, or adding skills/commands."
4
+ ---
5
+
6
+ # ドキュメント反映
7
+
8
+ `/update-docs` はコード変更に伴うドキュメント更新を行うスキル。
9
+ 機能追加・修正・スキル追加の後に呼ぶことで、ドキュメントの整合性を保つ。
10
+
11
+ ## 対象ドキュメント
12
+
13
+ ### このリポジトリ (runops 開発者向け)
14
+
15
+ | ファイル | 内容 | 更新タイミング |
16
+ |---------|------|---------------|
17
+ | `CLAUDE.md` | プロジェクト概要・主要コマンド表 | コマンド追加・変更時 |
18
+ | `.claude/rules/commands.md` | 全コマンド一覧 | CLI 引数追加・変更時 |
19
+ | `.claude/rules/dev-workflow.md` | 開発ワークフロー | ビルド手順・品質ゲート変更時 |
20
+ | `.claude/rules/architecture.md` | アーキテクチャ原則 | レイヤ構造変更時 |
21
+ | `docs/toml-reference.md` | TOML 設定リファレンス | case.toml / survey.toml 等のフィールド追加時 |
22
+ | `docs/extending.md` | 拡張ガイド | Adapter / Launcher の追加方法変更時 |
23
+ | `docs/project-flow.md` | プロジェクトフロー | ワークフロー変更時 |
24
+ | `src/runops/sites/*.md` | サイト固有ドキュメント | サイト機能追加・制限事項更新時 |
25
+
26
+ ### プロジェクト側ハーネス (runops ユーザー向け)
27
+
28
+ | テンプレート | 内容 | 更新タイミング |
29
+ |------------|------|---------------|
30
+ | `src/runops/templates/agent.md` | プロジェクト側 CLAUDE.md | コマンド体系変更時 |
31
+ | `src/runops/templates/scaffold/rules/runops-workflow.md` | ワークフロー rule | コマンド追加時 |
32
+ | `src/runops/templates/scaffold/rules/cookbook.md` | cookbook | 頻出パターン追加時 |
33
+
34
+ ## 手順
35
+
36
+ ### 1. 変更内容を把握する
37
+
38
+ ```bash
39
+ # 直近のコミットから (デフォルト)
40
+ git log --oneline HEAD~5..HEAD
41
+
42
+ # または未コミットの変更
43
+ git diff --stat
44
+ ```
45
+
46
+ ### 2. 影響するドキュメントを特定する
47
+
48
+ 変更の種類と対応するドキュメント:
49
+
50
+ | 変更の種類 | 更新対象 |
51
+ |-----------|---------|
52
+ | CLI オプション追加・変更 | `commands.md`, `CLAUDE.md`, `toml-reference.md` |
53
+ | TOML フィールド追加 | `toml-reference.md` |
54
+ | 新スキル追加 | プロジェクト側テンプレートの CLAUDE.md (スキル一覧があれば) |
55
+ | サイト固有の変更 | `src/runops/sites/<site>.md` |
56
+ | Adapter / Launcher 追加 | `docs/extending.md`, `CLAUDE.md` |
57
+ | アーキテクチャ変更 | `architecture.md` |
58
+
59
+ ### 3. ドキュメントを更新する
60
+
61
+ 各対象ファイルを読み、変更を反映する。
62
+
63
+ ### 4. 整合性チェック
64
+
65
+ - コマンドの `--help` 出力とドキュメントが一致しているか
66
+ - TOML フィールドの型・デフォルト値が正しいか
67
+ - スキルの description がファイル内容と一致しているか
68
+
69
+ ## 注意事項
70
+
71
+ - ドキュメントの書き方や文体は既存部分に合わせる
72
+ - 日本語ドキュメントと英語ドキュメントの両方を更新する
73
+ - ハーネス二重構造に注意: 開発者向け (`.claude/`) とプロジェクト側 (`src/runops/templates/`) は別物
@@ -93,7 +93,7 @@ completed → archived → purged
93
93
  |---------|------|
94
94
  | `runops init` / `setup` / `doctor` | プロジェクト管理 |
95
95
  | `runops case new` / `runs create` / `runs sweep` | case / run 生成 |
96
- | `runops runs submit [--all]` | ジョブ投入 |
96
+ | `runops runs submit [--all] [-qn] [--qos]` | ジョブ投入 |
97
97
  | `runops runs status` / `sync` / `log` / `dashboard` | モニタリング |
98
98
  | `runops analyze summarize` / `collect` | 解析 |
99
99
  | `runops notes append` / `knowledge save` | 知見管理 |
@@ -109,7 +109,7 @@ completed → archived → purged
109
109
  - docstring は Google style
110
110
  - テスト: Slurm はモック、TOML は fixtures、CLI は CliRunner
111
111
  - Git: 1 コミット = 1 論理変更、`--no-verify` / `--force` 禁止
112
- - release 時は `pyproject.toml` version を先に更新
112
+ - release 時は `pyproject.toml` + `__init__.py` の version を同時に更新
113
113
 
114
114
  ## Adapter 実装時の注意
115
115
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runops
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: HPC simulation run management CLI tool for Slurm-based environments
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -24,7 +24,7 @@ runops プロジェクトにおける Agent の作業ガイド。
24
24
  | sweep 内容を確認だけ | `runops runs sweep <survey> --dry-run` |
25
25
  | job 投入 | `runops runs submit` |
26
26
  | 全 run 一括投入 | `runops runs submit --all` |
27
- | キュー上書き / 依存ジョブ | `runops runs submit -qn <queue>` / `--afterok <job_id>` |
27
+ | キュー上書き / QOS / 依存ジョブ | `runops runs submit -qn <queue>` / `--qos <qos>` / `--afterok <job_id>` |
28
28
  | 状態確認 (単一/複数/survey 一括) | `runops runs status [RUNS...]` |
29
29
  | Slurm 同期 (単一/複数/survey 一括) | `runops runs sync [RUNS...]` (bulk: created + terminal state は silent skip) |
30
30
  | ログ確認 | `runops runs log` |
@@ -301,6 +301,7 @@ Slurm job parameters. These become `#SBATCH` directives in `job.sh`.
301
301
  | Field | Type | Required | Description |
302
302
  |-------|------|----------|-------------|
303
303
  | `partition` | string | No | Partition/queue name. Can be overridden at submit time with `runops runs submit -qn <name>` |
304
+ | `qos` | string | No | Slurm QOS name. Emits `#SBATCH --qos=<value>`. Can be overridden with `runops runs submit --qos <name>`. Note: camphor では使用不可 (partition 経由で暗黙決定) |
304
305
  | `nodes` | integer | No | Number of nodes |
305
306
  | `ntasks` | integer | No | Number of MPI tasks |
306
307
  | `walltime` | string | Yes | Wall time limit (HH:MM:SS) |
@@ -312,6 +313,7 @@ Slurm job parameters. These become `#SBATCH` directives in `job.sh`.
312
313
  | Field | Type | Required | Description |
313
314
  |-------|------|----------|-------------|
314
315
  | `partition` | string | No | Partition/queue name |
316
+ | `qos` | string | No | Slurm QOS name (camphor では使用不可) |
315
317
  | `processes` | integer | No | MPI プロセス数 (`--rsc p=N`) |
316
318
  | `threads` | integer | No | プロセスあたりスレッド数 (`--rsc t=T`) |
317
319
  | `cores` | integer | No | プロセスあたりコア数 (`--rsc c=C`, ≥ threads) |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "runops"
7
- version = "0.2.1"
7
+ version = "0.3.0"
8
8
  description = "HPC simulation run management CLI tool for Slurm-based environments"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -2,4 +2,4 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.1.9"
5
+ __version__ = "0.3.0"
@@ -702,7 +702,7 @@ class BeachAdapter(SimulatorAdapter):
702
702
  summary_file = output_dir / "summary.txt"
703
703
  if summary_file.is_file():
704
704
  try:
705
- for line in summary_file.read_text().split("\n"):
705
+ for line in summary_file.read_text(encoding="utf-8").split("\n"):
706
706
  line = line.strip()
707
707
  if "=" not in line:
708
708
  continue
@@ -765,7 +765,9 @@ class EmseAdapter(SimulatorAdapter):
765
765
  nstep = self._get_expected_nstep(run_dir)
766
766
  lines = [
767
767
  line
768
- for line in energy_file.read_text().strip().split("\n")
768
+ for line in energy_file.read_text(encoding="utf-8")
769
+ .strip()
770
+ .split("\n")
769
771
  if line.strip()
770
772
  ]
771
773
  if lines and nstep:
@@ -814,7 +816,9 @@ class EmseAdapter(SimulatorAdapter):
814
816
  try:
815
817
  lines = [
816
818
  line
817
- for line in energy_file.read_text().strip().split("\n")
819
+ for line in energy_file.read_text(encoding="utf-8")
820
+ .strip()
821
+ .split("\n")
818
822
  if line.strip()
819
823
  ]
820
824
  if lines:
@@ -273,7 +273,7 @@ class GenericAdapter(SimulatorAdapter):
273
273
 
274
274
  if exit_code_path.exists():
275
275
  try:
276
- code = int(exit_code_path.read_text().strip())
276
+ code = int(exit_code_path.read_text(encoding="utf-8").strip())
277
277
  except (ValueError, OSError):
278
278
  return "unknown"
279
279
  return "completed" if code == 0 else "failed"
@@ -316,7 +316,9 @@ class GenericAdapter(SimulatorAdapter):
316
316
  exit_code_path = run_dir / WORK_DIR / EXIT_CODE_FILE
317
317
  if exit_code_path.exists():
318
318
  try:
319
- summary["exit_code"] = int(exit_code_path.read_text().strip())
319
+ summary["exit_code"] = int(
320
+ exit_code_path.read_text(encoding="utf-8").strip()
321
+ )
320
322
  except (ValueError, OSError) as exc:
321
323
  errors.append(f"exit_code: {exc}")
322
324
 
@@ -265,7 +265,7 @@ def show(
265
265
  typer.echo(f"Insight not found: {name}", err=True)
266
266
  raise typer.Exit(code=1)
267
267
 
268
- typer.echo(path.read_text())
268
+ typer.echo(path.read_text(encoding="utf-8"))
269
269
 
270
270
 
271
271
  def sync(
@@ -852,7 +852,7 @@ def render() -> None:
852
852
  rel = imports_path.relative_to(root)
853
853
  typer.echo(f"Rendered: {rel}")
854
854
 
855
- content = imports_path.read_text().strip()
855
+ content = imports_path.read_text(encoding="utf-8").strip()
856
856
  if content:
857
857
  for line in content.split("\n"):
858
858
  if line.startswith("@"):
@@ -129,7 +129,13 @@ def sync(
129
129
  RunState.PURGED.value,
130
130
  }
131
131
 
132
+ from collections import Counter
133
+
132
134
  failures = 0
135
+ state_counts: Counter[str] = Counter()
136
+ changed = 0
137
+ total = 0
138
+
133
139
  for run_dir in targets:
134
140
  try:
135
141
  manifest = read_manifest(run_dir)
@@ -138,11 +144,15 @@ def sync(
138
144
  failures += 1
139
145
  continue
140
146
 
147
+ total += 1
148
+
141
149
  # In multi-target / bulk mode, runs without a job_id are skipped
142
150
  # silently — they typically belong to ``created`` runs that haven't
143
151
  # been submitted yet.
144
152
  if not manifest.job.get("job_id", ""):
145
153
  if multi:
154
+ state = str(manifest.run.get("status", "created"))
155
+ state_counts[state] += 1
146
156
  continue
147
157
  run_id_str = manifest.run.get("id", run_dir.name)
148
158
  typer.echo(
@@ -158,6 +168,7 @@ def sync(
158
168
  # in bulk mode so the rest of the survey still gets processed.
159
169
  current_state = str(manifest.run.get("status", ""))
160
170
  if current_state in terminal_states:
171
+ state_counts[current_state] += 1
161
172
  if multi:
162
173
  continue
163
174
  run_id_str = manifest.run.get("id", run_dir.name)
@@ -174,14 +185,38 @@ def sync(
174
185
  failures += 1
175
186
  continue
176
187
 
188
+ after = str(result.state_after or current_state)
189
+ state_counts[after] += 1
190
+
177
191
  if result.state_before == result.state_after:
178
192
  typer.echo(f"{run_id}: state unchanged ({result.state_after})")
179
193
  else:
194
+ changed += 1
180
195
  msg = f"{run_id}: {result.state_before} -> {result.state_after}"
181
196
  failure_reason = str(result.data.get("failure_reason", ""))
182
197
  if failure_reason:
183
198
  msg += f" (reason: {failure_reason})"
184
199
  typer.echo(msg)
185
200
 
201
+ # Print summary when multiple targets are involved
202
+ if multi and total > 0:
203
+ parts = []
204
+ for state in (
205
+ "completed",
206
+ "running",
207
+ "submitted",
208
+ "created",
209
+ "failed",
210
+ "cancelled",
211
+ "archived",
212
+ "purged",
213
+ ):
214
+ count = state_counts.get(state, 0)
215
+ if count:
216
+ parts.append(f"{count} {state}")
217
+ summary_line = ", ".join(parts)
218
+ typer.echo(f"\nSummary: {summary_line} ({total} total)")
219
+ typer.echo(f" {changed} state(s) changed in this sync")
220
+
186
221
  if failures:
187
222
  raise typer.Exit(code=1)