loopmath 0.3.0__tar.gz → 0.3.1__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 (412) hide show
  1. {loopmath-0.3.0 → loopmath-0.3.1}/PKG-INFO +10 -11
  2. {loopmath-0.3.0 → loopmath-0.3.1}/README.md +9 -10
  3. {loopmath-0.3.0 → loopmath-0.3.1}/pyproject.toml +1 -1
  4. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/__init__.py +1 -1
  5. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/cli.py +42 -16
  6. loopmath-0.3.1/src/loopmath/cli_errors.py +121 -0
  7. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/cli_registry.py +22 -11
  8. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/compare/__init__.py +56 -17
  9. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/compare/calibration.py +3 -2
  10. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/compare/checks.py +56 -13
  11. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/compare/commands.py +31 -20
  12. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/compare/model.py +2 -0
  13. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/output.py +5 -0
  14. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/budget.py +40 -6
  15. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/commands.py +23 -9
  16. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/status.py +5 -4
  17. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/common.py +14 -0
  18. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/runs.py +7 -4
  19. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath.egg-info/PKG-INFO +10 -11
  20. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath.egg-info/SOURCES.txt +5 -1
  21. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_cli_graph.py +1 -1
  22. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_research_cli.py +2 -1
  23. loopmath-0.3.1/tests/test_v031_help_labels.py +124 -0
  24. loopmath-0.3.1/tests/test_v031_json_errors_and_costs.py +202 -0
  25. loopmath-0.3.1/tests/test_v031_new_findings.py +132 -0
  26. {loopmath-0.3.0 → loopmath-0.3.1}/LICENSE +0 -0
  27. {loopmath-0.3.0 → loopmath-0.3.1}/setup.cfg +0 -0
  28. {loopmath-0.3.0 → loopmath-0.3.1}/spec/ocp-v0.2.schema.json +0 -0
  29. {loopmath-0.3.0 → loopmath-0.3.1}/spec/ocp-v0.schema.json +0 -0
  30. {loopmath-0.3.0 → loopmath-0.3.1}/spec/ocp_conformance.py +0 -0
  31. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/__main__.py +0 -0
  32. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapter_host.py +0 -0
  33. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/__init__.py +0 -0
  34. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/atrium.py +0 -0
  35. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/base.py +0 -0
  36. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/bb.py +0 -0
  37. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/bb_support.py +0 -0
  38. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/harness2.py +0 -0
  39. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/omp.py +0 -0
  40. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/omp_facts.py +0 -0
  41. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/omp_links.py +0 -0
  42. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/omp_parse.py +0 -0
  43. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/omp_types.py +0 -0
  44. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/opencode.py +0 -0
  45. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/opencode_store.py +0 -0
  46. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/orca.py +0 -0
  47. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/otel_genai.py +0 -0
  48. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/otel_genai_graph.py +0 -0
  49. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/otel_genai_parse.py +0 -0
  50. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/otel_genai_types.py +0 -0
  51. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/otel_genai_usage.py +0 -0
  52. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/paseo.py +0 -0
  53. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/pi.py +0 -0
  54. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/pi_support.py +0 -0
  55. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/adapters/registry.py +0 -0
  56. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/__init__.py +0 -0
  57. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/block.py +0 -0
  58. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/check_pymc.py +0 -0
  59. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/commands.py +0 -0
  60. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/compose.py +0 -0
  61. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/design.py +0 -0
  62. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/features.py +0 -0
  63. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/fit.py +0 -0
  64. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/forest.py +0 -0
  65. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/gaussian.py +0 -0
  66. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/lookahead.py +0 -0
  67. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/outcome.py +0 -0
  68. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/pricing.py +0 -0
  69. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/priors.py +0 -0
  70. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/rules.py +0 -0
  71. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/belief/state.py +0 -0
  72. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/builder/__init__.py +0 -0
  73. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/builder/cli.py +0 -0
  74. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/builder/context.py +0 -0
  75. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/builder/predict.py +0 -0
  76. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/builder/server.py +0 -0
  77. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/cli_graph.py +0 -0
  78. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/cli_support.py +0 -0
  79. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/compare/_thresholds.py +0 -0
  80. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/compare/calibrate.py +0 -0
  81. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/e0/__init__.py +0 -0
  82. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/e0/arms.py +0 -0
  83. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/e0/cli_e0.py +0 -0
  84. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/e0/estimate.py +0 -0
  85. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/e0/estimate_rework.py +0 -0
  86. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/e0/figures.py +0 -0
  87. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/e0/io.py +0 -0
  88. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/e0/verb.py +0 -0
  89. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/explain.py +0 -0
  90. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/fit.py +0 -0
  91. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/fit_assembly.py +0 -0
  92. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/fit_bayes.py +0 -0
  93. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/fit_masks.py +0 -0
  94. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/fit_predict.py +0 -0
  95. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/fit_pricing.py +0 -0
  96. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/fit_transfer.py +0 -0
  97. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/gaps.py +0 -0
  98. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/gate/__init__.py +0 -0
  99. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/gate/commands.py +0 -0
  100. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/gate/core.py +0 -0
  101. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/gate/inputs.py +0 -0
  102. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/gitwalk.py +0 -0
  103. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/grade.py +0 -0
  104. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/grade_support.py +0 -0
  105. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/__init__.py +0 -0
  106. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/artifact_git.py +0 -0
  107. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/artifact_join.py +0 -0
  108. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/artifact_kinds.py +0 -0
  109. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/artifacts.py +0 -0
  110. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/bashparse.py +0 -0
  111. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/bashread_ops.py +0 -0
  112. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/bashwrite_ops.py +0 -0
  113. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/bashwrites.py +0 -0
  114. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/cache.py +0 -0
  115. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/codexio.py +0 -0
  116. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/codexio_parse.py +0 -0
  117. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/codexio_scan.py +0 -0
  118. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/dataset.py +0 -0
  119. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/dataset_build.py +0 -0
  120. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/dataset_core.py +0 -0
  121. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/dataset_edges.py +0 -0
  122. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/dataset_nodes.py +0 -0
  123. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/extract.py +0 -0
  124. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/html_common.py +0 -0
  125. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/html_costcurve.py +0 -0
  126. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/html_css.py +0 -0
  127. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/html_data.py +0 -0
  128. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/html_force.py +0 -0
  129. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/html_render.py +0 -0
  130. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/html_swimlanes.py +0 -0
  131. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler.py +0 -0
  132. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_cli.py +0 -0
  133. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_common.py +0 -0
  134. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_parse.py +0 -0
  135. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_pricing.py +0 -0
  136. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_prompt.py +0 -0
  137. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_prompt_build.py +0 -0
  138. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_prompt_dispatch.py +0 -0
  139. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_prompt_evidence.py +0 -0
  140. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_prompt_node.py +0 -0
  141. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_prompt_records.py +0 -0
  142. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_prompt_result.py +0 -0
  143. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_prompt_session.py +0 -0
  144. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_prompt_tools.py +0 -0
  145. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_prompt_validation.py +0 -0
  146. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_prompt_workflow.py +0 -0
  147. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_report.py +0 -0
  148. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labeler_score.py +0 -0
  149. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/labels.py +0 -0
  150. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/launch.py +0 -0
  151. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/launch_origin.py +0 -0
  152. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/ocp.py +0 -0
  153. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/ocp_emit.py +0 -0
  154. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/ocp_support.py +0 -0
  155. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/render.py +0 -0
  156. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/runfile.py +0 -0
  157. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/runfile_export.py +0 -0
  158. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/runfile_finish.py +0 -0
  159. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/scan.py +0 -0
  160. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/schema.py +0 -0
  161. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/graph/token_completeness.py +0 -0
  162. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/harness/__init__.py +0 -0
  163. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/harness/layouts.py +0 -0
  164. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/harness_runs/__init__.py +0 -0
  165. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/harness_runs/commands.py +0 -0
  166. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/harness_runs/costs.py +0 -0
  167. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/harness_runs/ocp.py +0 -0
  168. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/harness_runs/rows.py +0 -0
  169. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/harness_runs/streams.py +0 -0
  170. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ingest/__init__.py +0 -0
  171. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ingest/base.py +0 -0
  172. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ingest/claude_code.py +0 -0
  173. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ingest/claude_code_support.py +0 -0
  174. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ingest/codex.py +0 -0
  175. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ingest/codex_support.py +0 -0
  176. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ingest/ocp.py +0 -0
  177. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ingest/ocp_analysis.py +0 -0
  178. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ingest/ocp_artifact.py +0 -0
  179. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ingest/ocp_common.py +0 -0
  180. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ingest/ocp_convert.py +0 -0
  181. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ingest/ocp_projection.py +0 -0
  182. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/logmatch/__init__.py +0 -0
  183. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/logmatch/artifacts.py +0 -0
  184. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/logmatch/clip.py +0 -0
  185. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/logmatch/codex_window.py +0 -0
  186. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/logmatch/costs.py +0 -0
  187. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/logmatch/match.py +0 -0
  188. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/logmatch/settle.py +0 -0
  189. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/logmatch/tariff.py +0 -0
  190. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ocp/__init__.py +0 -0
  191. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ocp/canonical.py +0 -0
  192. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ocp/commands.py +0 -0
  193. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ocp/conformance.py +0 -0
  194. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ocp/contractv3.py +0 -0
  195. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ocp/emit.py +0 -0
  196. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ocp/graph_contract.py +0 -0
  197. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ocp/membership.py +0 -0
  198. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ocp/migrate.py +0 -0
  199. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ocp/schema/ocp-v0.2.schema.json +0 -0
  200. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ocp/schema/ocp-v0.3.schema.json +0 -0
  201. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ocp/schema/ocp-v0.schema.json +0 -0
  202. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/ocp/version.py +0 -0
  203. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/onboard/__init__.py +0 -0
  204. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/onboard/commands.py +0 -0
  205. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/onboard/history.py +0 -0
  206. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/onboard/label.py +0 -0
  207. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/onboard/usual.py +0 -0
  208. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/pool.py +0 -0
  209. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/price.py +0 -0
  210. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/price_validation.py +0 -0
  211. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/prices.toml +0 -0
  212. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/__init__.py +0 -0
  213. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/benchmarks.py +0 -0
  214. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/benchmarks.toml +0 -0
  215. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/build.py +0 -0
  216. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/bundle/README.md +0 -0
  217. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/bundle/e0.jsonl.gz +0 -0
  218. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/bundle/lanes.jsonl.gz +0 -0
  219. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/bundle/manifest.json +0 -0
  220. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/bundle/rq1.jsonl.gz +0 -0
  221. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/bundle/sweep.jsonl.gz +0 -0
  222. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/commands.py +0 -0
  223. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/e0.py +0 -0
  224. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/history.py +0 -0
  225. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/lanes.py +0 -0
  226. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/ocpdoc.py +0 -0
  227. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/reduce.py +0 -0
  228. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/registry.py +0 -0
  229. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/rq1.py +0 -0
  230. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/show.py +0 -0
  231. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/sweep.py +0 -0
  232. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/priors/validate.py +0 -0
  233. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/receipts.py +0 -0
  234. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/recommend/__init__.py +0 -0
  235. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/recommend/backlog.py +0 -0
  236. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/recommend/commands.py +0 -0
  237. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/recommend/curve.py +0 -0
  238. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/recommend/engine.py +0 -0
  239. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/recommend/gain.py +0 -0
  240. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/recommend/message.py +0 -0
  241. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/recommend/receipts.py +0 -0
  242. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/recommend/search.py +0 -0
  243. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/recommend/stats.py +0 -0
  244. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/recommend/storeread.py +0 -0
  245. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/recommend/variants.py +0 -0
  246. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/report/__init__.py +0 -0
  247. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/report/eligibility.py +0 -0
  248. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/report/html.py +0 -0
  249. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/report/terminal.py +0 -0
  250. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/report/terminal_exclusions.py +0 -0
  251. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/report/terminal_format.py +0 -0
  252. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/report/terminal_read.py +0 -0
  253. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/research_defaults.py +0 -0
  254. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/research_paths.py +0 -0
  255. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/scoring.py +0 -0
  256. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/scoring_comparisons.py +0 -0
  257. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/scoring_predictive.py +0 -0
  258. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/share/__init__.py +0 -0
  259. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/share/commands.py +0 -0
  260. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/share/export.py +0 -0
  261. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/share/import_.py +0 -0
  262. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/skill/__init__.py +0 -0
  263. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/skill/commands.py +0 -0
  264. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/skill/doctor.py +0 -0
  265. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/skill/install.py +0 -0
  266. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/skill/skills/loopmath/SKILL.md +0 -0
  267. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/skill/skills/loopmath-import-runs/SKILL.md +0 -0
  268. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/skill/skills/loopmath-onboard/SKILL.md +0 -0
  269. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/skill/skills/loopmath-plan-task/SKILL.md +0 -0
  270. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/skill/skills/loopmath-record-run/SKILL.md +0 -0
  271. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/skill/skills/loopmath-update-fit/SKILL.md +0 -0
  272. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/skill/skills/reference.md +0 -0
  273. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/__init__.py +0 -0
  274. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/config.py +0 -0
  275. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/finish.py +0 -0
  276. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/fitjob.py +0 -0
  277. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/home.py +0 -0
  278. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/ids.py +0 -0
  279. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/lock.py +0 -0
  280. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/record.py +0 -0
  281. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/report.py +0 -0
  282. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/rule_change.py +0 -0
  283. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/run_names.py +0 -0
  284. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/runs.py +0 -0
  285. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/store/verify.py +0 -0
  286. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/surface.py +0 -0
  287. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/surface_bootstrap.py +0 -0
  288. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/surface_estimation.py +0 -0
  289. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/surface_frame.py +0 -0
  290. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/surface_presentation.py +0 -0
  291. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/surface_support.py +0 -0
  292. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/taskmodel.py +0 -0
  293. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/types.py +0 -0
  294. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/__init__.py +0 -0
  295. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/README.md +0 -0
  296. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/base.css +0 -0
  297. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/builder.css +0 -0
  298. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/builder.html +0 -0
  299. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/builder.js +0 -0
  300. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/common.js +0 -0
  301. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/estimates.css +0 -0
  302. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/estimates.js +0 -0
  303. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/graph.css +0 -0
  304. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/graph.js +0 -0
  305. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/plans.css +0 -0
  306. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/plans.js +0 -0
  307. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/results.css +0 -0
  308. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/results.js +0 -0
  309. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/runs.js +0 -0
  310. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/viz.css +0 -0
  311. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/assets/viz.js +0 -0
  312. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/plans.py +0 -0
  313. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/views/posterior.py +0 -0
  314. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/__init__.py +0 -0
  315. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/candidates.py +0 -0
  316. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/catalog/best_of_n.toml +0 -0
  317. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/catalog/implement_review.toml +0 -0
  318. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/catalog/plan_implement.toml +0 -0
  319. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/catalog/plan_implement_review.toml +0 -0
  320. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/catalog/solo.toml +0 -0
  321. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/catalog/swarm.toml +0 -0
  322. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/commands.py +0 -0
  323. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/diff.py +0 -0
  324. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/format.py +0 -0
  325. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/graphview.py +0 -0
  326. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/ids.py +0 -0
  327. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/infer.py +0 -0
  328. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/models.py +0 -0
  329. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/ocp.py +0 -0
  330. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath/workflows/shapes.py +0 -0
  331. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath.egg-info/dependency_links.txt +0 -0
  332. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath.egg-info/entry_points.txt +0 -0
  333. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath.egg-info/requires.txt +0 -0
  334. {loopmath-0.3.0 → loopmath-0.3.1}/src/loopmath.egg-info/top_level.txt +0 -0
  335. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_adapter_atrium.py +0 -0
  336. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_adapter_bb.py +0 -0
  337. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_adapter_harness2.py +0 -0
  338. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_adapter_omp.py +0 -0
  339. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_adapter_opencode.py +0 -0
  340. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_adapter_opencode_output_tokens.py +0 -0
  341. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_adapter_orca.py +0 -0
  342. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_adapter_otel_genai.py +0 -0
  343. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_adapter_paseo.py +0 -0
  344. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_adapter_services.py +0 -0
  345. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_adapters.py +0 -0
  346. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_adapters_pi.py +0 -0
  347. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_analyze_cli.py +0 -0
  348. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_arms.py +0 -0
  349. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_cli_help_lists.py +0 -0
  350. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_cli_labeler.py +0 -0
  351. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_cli_main_module.py +0 -0
  352. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_cli_plan_hidden.py +0 -0
  353. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_cli_prices.py +0 -0
  354. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_cli_startup.py +0 -0
  355. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_color_proof.py +0 -0
  356. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_duplicate_guards.py +0 -0
  357. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_estimate.py +0 -0
  358. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_explain.py +0 -0
  359. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_fit.py +0 -0
  360. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_gaps.py +0 -0
  361. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_gitwalk.py +0 -0
  362. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_grade.py +0 -0
  363. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_artifact_recording.py +0 -0
  364. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_artifacts.py +0 -0
  365. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_bashwrites.py +0 -0
  366. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_cache.py +0 -0
  367. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_codexio.py +0 -0
  368. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_dataset.py +0 -0
  369. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_extract.py +0 -0
  370. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_glue.py +0 -0
  371. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_html.py +0 -0
  372. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_labeler.py +0 -0
  373. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_labeler_evidence_matrix.py +0 -0
  374. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_launch.py +0 -0
  375. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_ocp.py +0 -0
  376. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_render.py +0 -0
  377. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_runfile.py +0 -0
  378. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_graph_schema.py +0 -0
  379. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_home_guard.py +0 -0
  380. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_html.py +0 -0
  381. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_ingest.py +0 -0
  382. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_ingest_claude_code.py +0 -0
  383. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_ingest_codex.py +0 -0
  384. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_ingest_ocp.py +0 -0
  385. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_io.py +0 -0
  386. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_not_run_summary.py +0 -0
  387. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_ocp_conformance.py +0 -0
  388. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_ocp_golden.py +0 -0
  389. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_ocp_open_enums.py +0 -0
  390. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_ocp_versioning.py +0 -0
  391. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_output_html_path.py +0 -0
  392. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_packaging_metadata.py +0 -0
  393. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_packaging_smoke.py +0 -0
  394. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_pool.py +0 -0
  395. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_preview_script.py +0 -0
  396. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_price.py +0 -0
  397. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_receipts.py +0 -0
  398. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_release_paths.py +0 -0
  399. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_report_eligibility.py +0 -0
  400. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_runfile_privacy_n1b.py +0 -0
  401. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_scoring.py +0 -0
  402. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_split_rebindings.py +0 -0
  403. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_starting_prior.py +0 -0
  404. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_surface.py +0 -0
  405. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_taskmodel_features.py +0 -0
  406. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_terminal.py +0 -0
  407. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_types_control.py +0 -0
  408. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_types_piece_prediction.py +0 -0
  409. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_v01_scaffold.py +0 -0
  410. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_v027_p3.py +0 -0
  411. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_v027_small_fixes.py +0 -0
  412. {loopmath-0.3.0 → loopmath-0.3.1}/tests/test_v028_sol_6_1.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loopmath
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Plan agent workflows, record how they went, and learn from your own Claude Code and Codex logs
5
5
  Author: QKV Labs
6
6
  License-Expression: MIT
@@ -177,9 +177,12 @@ The workflow builder is the one page that is served: `loopmath builder --type bu
177
177
  | Record | `run` (start, record, import, attempt, artifact, finish, sessions), `outcome`, `budget`, `config` (get, set) |
178
178
  | Learn | `fit`, `onboard`, `share`, `prior` |
179
179
  | View | `runs`, `posterior`, `status`, `report`, `doctor`, `verify-receipts` |
180
+ | Harness testing | `check-change`, `gate` (experimental) |
180
181
  | Skill | `skill` (install, uninstall, show) |
181
182
  | OCP | `ocp` (validate, migrate) |
182
- | Log analysis | `analyze`, `graph`, `adapt`, `prices`, `validate-prices`, `scan` |
183
+ | Log analysis | `analyze`, `graph`, `adapt`, `prices`, `validate-prices` |
184
+
185
+ These are experimental: `gate`, the harness-part estimates (`recommend --harness-variants` and `--harness`, `posterior --level harness` and its `factors`), `adapt harness2` and the ADE adapters (`adapt NAME`). Each works and has tests, but has had little use on real work so far, and its output may change. Their help says so too.
183
186
 
184
187
  Each command prints a short summary by default. With `--json`, it prints exactly one JSON object instead. One exception: `loopmath adapt harness2` and `adapt harness-runs` without `--out` print the run documents on stdout, one per line, so their `--json` summary goes to stderr. Exit codes: 0 ok, 1 user error, 2 not found, 3 not implemented, 4 store locked, 5 no fit yet. For flags, `loopmath <command> --help` is the source of truth. Commands, pages and the `runs` table show times in your local time with the zone; the store and `--json` keep UTC. A cost under a cent shows one significant digit (`$0.004`), and an exact zero is `$0`.
185
188
 
@@ -255,21 +258,21 @@ A run can record the harness it ran with: the prompt files, skills, plugins, MCP
255
258
  `cost` is the multiplier on an attempt's dollars when the part is present; `success` is percentage points.
256
259
  - `loopmath recommend --harness-variants` prices the trees your runs used most, and `--harness PATH` prices one more, against the bare harness label on the same draws.
257
260
 
258
- An estimate is the change when a named part is present, pooled over its versions. It is not the effect of one edit, and a part on every run cannot be told apart from its harness. The cost ranges met their stated coverage in simulation. **The success ranges did not:** they pull effects toward 0, and their 80% ranges held 60% of nonzero effects. So they are marked `calibrated: false`, and so are the success-side numbers of a priced variant. With no record anywhere, every number is what it was before. The record, the digest, the factor map and the limits are in [`spec/harness.md`](spec/harness.md).
261
+ An estimate is the change when a named part is present, pooled over its versions. It is not the effect of one edit, and a part on every run cannot be told apart from its harness. The cost ranges met their stated coverage in simulation. **The success ranges did not:** they pull effects toward 0, and their 80% ranges held 60% of nonzero effects. So they are marked `calibrated: false`, and so are the success-side numbers of a priced variant, and the harness-part estimates are experimental. With no record anywhere, every number is what it was before. The record, the digest, the factor map and the limits are in [`spec/harness.md`](spec/harness.md).
259
262
 
260
263
  ## Testing a harness change
261
264
 
262
265
  These commands are for changing the harness your agents run with and finding out whether the change paid off.
263
266
 
264
267
  - **Harness parts and `--harness`.** Record the tree each run used (`run finish --harness auto` or `PATH`), and the fit learns each named part's effect, as in the section above. `recommend --harness-variants` prices the trees your runs used on the configurations those runs used, and on the recommendation's own when their harness has data on the parts; `--harness PATH` prices one more tree. Where a tree is not priced, `not_priced` in `--json` says why. The record, the factor map and the limits are in [`spec/harness.md`](spec/harness.md).
265
- - **`adapt harness-runs` and `adapt harness2`.** These bring in runs another tool made. `loopmath adapt harness-runs FILE --out DIR` reads a `loopmath.harness-runs/0.1` file, one JSON line per rollout, as an auto-research loop, a plugin eval suite or a hand A/B writes it. `loopmath adapt harness2 RUNS --out DIR` reads a harness2 runs folder. Then `loopmath run import DIR --finish` stores the runs. The row format, outcomes, costs and worked examples are in [`spec/harness-runs.md`](spec/harness-runs.md).
268
+ - **`adapt harness-runs` and `adapt harness2`.** These bring in runs another tool made; `adapt harness2` is experimental, since its input format is read from harness2's code and it has not yet read a real runs folder. `loopmath adapt harness-runs FILE --out DIR` reads a `loopmath.harness-runs/0.1` file, one JSON line per rollout, as an auto-research loop, a plugin eval suite or a hand A/B writes it. `loopmath adapt harness2 RUNS --out DIR` reads a harness2 runs folder. Then `loopmath run import DIR --finish` stores the runs. The row format, outcomes, costs and worked examples are in [`spec/harness-runs.md`](spec/harness-runs.md).
266
269
  - **`check-change`.** It decides whether a change beats what it replaces, on paired runs:
267
270
  - `check-change start --base LABEL|PATH --cand LABEL|PATH --n N` plans the pairs and each pair's arm order;
268
- - `check-change status` gives the comparison, the verdict once there is one (adopt or reject) and the next pair worth running;
271
+ - `check-change status` gives the comparison, the verdict once there is one (adopt or reject) and the next pair worth running. A check that reaches its cap or spends its budget before the evidence decides keeps the base and says `not decided`. The next pair's value and price are shown at each look and stop nothing; `--value-stop` also stops a check, not decided, when the next pair is worth less than its price, from 4 scored pairs on (`--min-pairs K`); `--json` keeps `verdict: reject` there, with `comparison.stop` and `comparison.decided: false`;
269
272
  - `check-change from-runs --base-label SEL --cand-label SEL` compares finished runs already in the store, picked by a run label, a configuration id, a harness digest (a prefix of one digest will do) or `arm:NAME`.
270
273
 
271
274
  `--rule` names what counts as accepted, and `loopmath check-change --help` lists the rest. A run with no dollar cost leaves the decision to the outcomes alone, and the cost reads unknown; a budget or `--objective cpar` then refuses, naming the runs without one.
272
- - **`gate`.** For a loop that adopts changes on its own, it answers: adopt the candidate, reject it and keep the base, or replicate N more pairs first. `loopmath gate --base SEL --cand SEL` reads the finished runs in the store, or `--runs FILE` reads `loopmath.harness-runs/0.1` rows; an arm is a harness digest (`sha256:<hex>` or `digest:sha256:<hex>`, a prefix of one digest will do), a configuration id, or `arm=NAME` or `arm:NAME`; `check-change from-runs` takes the same forms. `--rule` takes the built-in rules or a rule defined under `[rules.NAME]` in the store's config, which the gate reads as `check-change` does, also with `--runs`. The comparison of the paired runs is `check-change`'s, and the gate adds the loop's policy to it: no adoption before `--r-min` pairs, looks at 1, 2, 4 and so on pairs up to `--r-max`, a `--floor` (the smallest gain worth adopting, 0 or more; under an acceptance rule it applies to the cut in cost per accepted result, a fraction, or to the gain in the chance of acceptance when a cost is unknown), and the dollars, `--budget-usd` for both arms (0 buys no further pair) and `--max-cost-ratio`. With no evidence, an arm that matches no run or a rule that scores no pair, it exits 1 and names the arms or the rules that are there. Both dollar flags need every run's cost and refuse, naming the runs without one; with neither flag, a run with no recorded cost leaves the decision to the outcomes alone, and the cost reads unknown, never $0. When nothing resolves within the cap or the budget, it keeps the base and says where it stopped. In Python, `loopmath.gate.decide(base_runs, cand_runs, GatePolicy(...))` gives the same answer and writes nothing; `loopmath gate --help` lists the rest.
275
+ - **`gate` (experimental).** For a loop that adopts changes on its own, it answers: adopt the candidate, reject it and keep the base, or replicate N more pairs first. `loopmath gate --base SEL --cand SEL` reads the finished runs in the store, or `--runs FILE` reads `loopmath.harness-runs/0.1` rows; an arm is a harness digest (`sha256:<hex>` or `digest:sha256:<hex>`, a prefix of one digest will do), a configuration id, or `arm=NAME` or `arm:NAME`; `check-change from-runs` takes the same forms. `--rule` takes the built-in rules or a rule defined under `[rules.NAME]` in the store's config, which the gate reads as `check-change` does, also with `--runs`. The comparison of the paired runs is `check-change`'s, and the gate adds the loop's policy to it: no adoption before `--r-min` pairs, looks at 1, 2, 4 and so on pairs up to `--r-max`, a `--floor` (the smallest gain worth adopting, 0 or more; under an acceptance rule it applies to the cut in cost per accepted result, a fraction, or to the gain in the chance of acceptance when a cost is unknown), and the dollars, `--budget-usd` for both arms (0 buys no further pair) and `--max-cost-ratio`. With no evidence, an arm that matches no run or a rule that scores no pair, it exits 1 and names the arms or the rules that are there. Both dollar flags need every run's cost and refuse, naming the runs without one; with neither flag, a run with no recorded cost leaves the decision to the outcomes alone, and the cost reads unknown, never $0. When nothing resolves within the cap or the budget, it keeps the base and says where it stopped. In Python, `loopmath.gate.decide(base_runs, cand_runs, GatePolicy(...))` gives the same answer and writes nothing; `loopmath gate --help` lists the rest.
273
276
 
274
277
  ## The store and privacy
275
278
 
@@ -302,11 +305,7 @@ loopmath graph --workspace NAME --format html --out graph.html
302
305
 
303
306
  `loopmath prices` lists the packaged prices with the source of each. A rate marked "rate not confirmed" is a best public-price guess, and every dollar figure priced with one says so.
304
307
 
305
- `graph` rebuilds which agent launched which, and which session read what another wrote. Its formats are `ocp`, `json`, `dot`, `run` and `html`. `graph --out FILE` and `adapt NAME --out FILE` write to any path you name: missing folders are created, an existing file is replaced, and a directory is refused. `graph` prints a short summary on stderr; `--verbose` adds every count of what was skipped or could not be placed. See the [workflow graph guide](docs/graph.md) and [adapters](docs/adapters.md).
306
-
307
- ## Research data
308
-
309
- `loopmath research --help` lists the verbs that read research data folders, which are not part of the package: `research fit` and `research transfer-test` read sweep run records, and `research analyze-e0` reads a session corpus. Name the folders with a flag (`--sweep-dir`, `--corpus`), an environment variable (`LOOPMATH_SWEEP_DIR`, `LOOPMATH_E0_CORPUS`), or config (`loopmath config set research.sweep_dir PATH`, `research.e0_corpus`). `research fit` and `research transfer-test` need the `bayes` extra (`pip install 'loopmath[bayes]'`), and `research analyze-e0` the `e0` extra. `loopmath prior build --out DIR` rebuilds the packaged prior bundle from research data folders like these; `loopmath prior build --help` names them.
308
+ `graph` rebuilds which agent launched which, and which session read what another wrote. Its formats are `ocp`, `json`, `dot`, `run` and `html`. `graph --out FILE` and `adapt NAME --out FILE` write to any path you name: missing folders are created, an existing file is replaced, and a directory is refused. `graph` prints a short summary on stderr; `--verbose` adds every count of what was skipped or could not be placed. See the [workflow graph guide](docs/graph.md) and [adapters](docs/adapters.md). The ADE adapters (`adapt NAME`) are experimental: each is tested on a synthetic fixture written from its tool's format, not yet on a real store.
310
309
 
311
310
  ## More
312
311
 
@@ -139,9 +139,12 @@ The workflow builder is the one page that is served: `loopmath builder --type bu
139
139
  | Record | `run` (start, record, import, attempt, artifact, finish, sessions), `outcome`, `budget`, `config` (get, set) |
140
140
  | Learn | `fit`, `onboard`, `share`, `prior` |
141
141
  | View | `runs`, `posterior`, `status`, `report`, `doctor`, `verify-receipts` |
142
+ | Harness testing | `check-change`, `gate` (experimental) |
142
143
  | Skill | `skill` (install, uninstall, show) |
143
144
  | OCP | `ocp` (validate, migrate) |
144
- | Log analysis | `analyze`, `graph`, `adapt`, `prices`, `validate-prices`, `scan` |
145
+ | Log analysis | `analyze`, `graph`, `adapt`, `prices`, `validate-prices` |
146
+
147
+ These are experimental: `gate`, the harness-part estimates (`recommend --harness-variants` and `--harness`, `posterior --level harness` and its `factors`), `adapt harness2` and the ADE adapters (`adapt NAME`). Each works and has tests, but has had little use on real work so far, and its output may change. Their help says so too.
145
148
 
146
149
  Each command prints a short summary by default. With `--json`, it prints exactly one JSON object instead. One exception: `loopmath adapt harness2` and `adapt harness-runs` without `--out` print the run documents on stdout, one per line, so their `--json` summary goes to stderr. Exit codes: 0 ok, 1 user error, 2 not found, 3 not implemented, 4 store locked, 5 no fit yet. For flags, `loopmath <command> --help` is the source of truth. Commands, pages and the `runs` table show times in your local time with the zone; the store and `--json` keep UTC. A cost under a cent shows one significant digit (`$0.004`), and an exact zero is `$0`.
147
150
 
@@ -217,21 +220,21 @@ A run can record the harness it ran with: the prompt files, skills, plugins, MCP
217
220
  `cost` is the multiplier on an attempt's dollars when the part is present; `success` is percentage points.
218
221
  - `loopmath recommend --harness-variants` prices the trees your runs used most, and `--harness PATH` prices one more, against the bare harness label on the same draws.
219
222
 
220
- An estimate is the change when a named part is present, pooled over its versions. It is not the effect of one edit, and a part on every run cannot be told apart from its harness. The cost ranges met their stated coverage in simulation. **The success ranges did not:** they pull effects toward 0, and their 80% ranges held 60% of nonzero effects. So they are marked `calibrated: false`, and so are the success-side numbers of a priced variant. With no record anywhere, every number is what it was before. The record, the digest, the factor map and the limits are in [`spec/harness.md`](spec/harness.md).
223
+ An estimate is the change when a named part is present, pooled over its versions. It is not the effect of one edit, and a part on every run cannot be told apart from its harness. The cost ranges met their stated coverage in simulation. **The success ranges did not:** they pull effects toward 0, and their 80% ranges held 60% of nonzero effects. So they are marked `calibrated: false`, and so are the success-side numbers of a priced variant, and the harness-part estimates are experimental. With no record anywhere, every number is what it was before. The record, the digest, the factor map and the limits are in [`spec/harness.md`](spec/harness.md).
221
224
 
222
225
  ## Testing a harness change
223
226
 
224
227
  These commands are for changing the harness your agents run with and finding out whether the change paid off.
225
228
 
226
229
  - **Harness parts and `--harness`.** Record the tree each run used (`run finish --harness auto` or `PATH`), and the fit learns each named part's effect, as in the section above. `recommend --harness-variants` prices the trees your runs used on the configurations those runs used, and on the recommendation's own when their harness has data on the parts; `--harness PATH` prices one more tree. Where a tree is not priced, `not_priced` in `--json` says why. The record, the factor map and the limits are in [`spec/harness.md`](spec/harness.md).
227
- - **`adapt harness-runs` and `adapt harness2`.** These bring in runs another tool made. `loopmath adapt harness-runs FILE --out DIR` reads a `loopmath.harness-runs/0.1` file, one JSON line per rollout, as an auto-research loop, a plugin eval suite or a hand A/B writes it. `loopmath adapt harness2 RUNS --out DIR` reads a harness2 runs folder. Then `loopmath run import DIR --finish` stores the runs. The row format, outcomes, costs and worked examples are in [`spec/harness-runs.md`](spec/harness-runs.md).
230
+ - **`adapt harness-runs` and `adapt harness2`.** These bring in runs another tool made; `adapt harness2` is experimental, since its input format is read from harness2's code and it has not yet read a real runs folder. `loopmath adapt harness-runs FILE --out DIR` reads a `loopmath.harness-runs/0.1` file, one JSON line per rollout, as an auto-research loop, a plugin eval suite or a hand A/B writes it. `loopmath adapt harness2 RUNS --out DIR` reads a harness2 runs folder. Then `loopmath run import DIR --finish` stores the runs. The row format, outcomes, costs and worked examples are in [`spec/harness-runs.md`](spec/harness-runs.md).
228
231
  - **`check-change`.** It decides whether a change beats what it replaces, on paired runs:
229
232
  - `check-change start --base LABEL|PATH --cand LABEL|PATH --n N` plans the pairs and each pair's arm order;
230
- - `check-change status` gives the comparison, the verdict once there is one (adopt or reject) and the next pair worth running;
233
+ - `check-change status` gives the comparison, the verdict once there is one (adopt or reject) and the next pair worth running. A check that reaches its cap or spends its budget before the evidence decides keeps the base and says `not decided`. The next pair's value and price are shown at each look and stop nothing; `--value-stop` also stops a check, not decided, when the next pair is worth less than its price, from 4 scored pairs on (`--min-pairs K`); `--json` keeps `verdict: reject` there, with `comparison.stop` and `comparison.decided: false`;
231
234
  - `check-change from-runs --base-label SEL --cand-label SEL` compares finished runs already in the store, picked by a run label, a configuration id, a harness digest (a prefix of one digest will do) or `arm:NAME`.
232
235
 
233
236
  `--rule` names what counts as accepted, and `loopmath check-change --help` lists the rest. A run with no dollar cost leaves the decision to the outcomes alone, and the cost reads unknown; a budget or `--objective cpar` then refuses, naming the runs without one.
234
- - **`gate`.** For a loop that adopts changes on its own, it answers: adopt the candidate, reject it and keep the base, or replicate N more pairs first. `loopmath gate --base SEL --cand SEL` reads the finished runs in the store, or `--runs FILE` reads `loopmath.harness-runs/0.1` rows; an arm is a harness digest (`sha256:<hex>` or `digest:sha256:<hex>`, a prefix of one digest will do), a configuration id, or `arm=NAME` or `arm:NAME`; `check-change from-runs` takes the same forms. `--rule` takes the built-in rules or a rule defined under `[rules.NAME]` in the store's config, which the gate reads as `check-change` does, also with `--runs`. The comparison of the paired runs is `check-change`'s, and the gate adds the loop's policy to it: no adoption before `--r-min` pairs, looks at 1, 2, 4 and so on pairs up to `--r-max`, a `--floor` (the smallest gain worth adopting, 0 or more; under an acceptance rule it applies to the cut in cost per accepted result, a fraction, or to the gain in the chance of acceptance when a cost is unknown), and the dollars, `--budget-usd` for both arms (0 buys no further pair) and `--max-cost-ratio`. With no evidence, an arm that matches no run or a rule that scores no pair, it exits 1 and names the arms or the rules that are there. Both dollar flags need every run's cost and refuse, naming the runs without one; with neither flag, a run with no recorded cost leaves the decision to the outcomes alone, and the cost reads unknown, never $0. When nothing resolves within the cap or the budget, it keeps the base and says where it stopped. In Python, `loopmath.gate.decide(base_runs, cand_runs, GatePolicy(...))` gives the same answer and writes nothing; `loopmath gate --help` lists the rest.
237
+ - **`gate` (experimental).** For a loop that adopts changes on its own, it answers: adopt the candidate, reject it and keep the base, or replicate N more pairs first. `loopmath gate --base SEL --cand SEL` reads the finished runs in the store, or `--runs FILE` reads `loopmath.harness-runs/0.1` rows; an arm is a harness digest (`sha256:<hex>` or `digest:sha256:<hex>`, a prefix of one digest will do), a configuration id, or `arm=NAME` or `arm:NAME`; `check-change from-runs` takes the same forms. `--rule` takes the built-in rules or a rule defined under `[rules.NAME]` in the store's config, which the gate reads as `check-change` does, also with `--runs`. The comparison of the paired runs is `check-change`'s, and the gate adds the loop's policy to it: no adoption before `--r-min` pairs, looks at 1, 2, 4 and so on pairs up to `--r-max`, a `--floor` (the smallest gain worth adopting, 0 or more; under an acceptance rule it applies to the cut in cost per accepted result, a fraction, or to the gain in the chance of acceptance when a cost is unknown), and the dollars, `--budget-usd` for both arms (0 buys no further pair) and `--max-cost-ratio`. With no evidence, an arm that matches no run or a rule that scores no pair, it exits 1 and names the arms or the rules that are there. Both dollar flags need every run's cost and refuse, naming the runs without one; with neither flag, a run with no recorded cost leaves the decision to the outcomes alone, and the cost reads unknown, never $0. When nothing resolves within the cap or the budget, it keeps the base and says where it stopped. In Python, `loopmath.gate.decide(base_runs, cand_runs, GatePolicy(...))` gives the same answer and writes nothing; `loopmath gate --help` lists the rest.
235
238
 
236
239
  ## The store and privacy
237
240
 
@@ -264,11 +267,7 @@ loopmath graph --workspace NAME --format html --out graph.html
264
267
 
265
268
  `loopmath prices` lists the packaged prices with the source of each. A rate marked "rate not confirmed" is a best public-price guess, and every dollar figure priced with one says so.
266
269
 
267
- `graph` rebuilds which agent launched which, and which session read what another wrote. Its formats are `ocp`, `json`, `dot`, `run` and `html`. `graph --out FILE` and `adapt NAME --out FILE` write to any path you name: missing folders are created, an existing file is replaced, and a directory is refused. `graph` prints a short summary on stderr; `--verbose` adds every count of what was skipped or could not be placed. See the [workflow graph guide](docs/graph.md) and [adapters](docs/adapters.md).
268
-
269
- ## Research data
270
-
271
- `loopmath research --help` lists the verbs that read research data folders, which are not part of the package: `research fit` and `research transfer-test` read sweep run records, and `research analyze-e0` reads a session corpus. Name the folders with a flag (`--sweep-dir`, `--corpus`), an environment variable (`LOOPMATH_SWEEP_DIR`, `LOOPMATH_E0_CORPUS`), or config (`loopmath config set research.sweep_dir PATH`, `research.e0_corpus`). `research fit` and `research transfer-test` need the `bayes` extra (`pip install 'loopmath[bayes]'`), and `research analyze-e0` the `e0` extra. `loopmath prior build --out DIR` rebuilds the packaged prior bundle from research data folders like these; `loopmath prior build --help` names them.
270
+ `graph` rebuilds which agent launched which, and which session read what another wrote. Its formats are `ocp`, `json`, `dot`, `run` and `html`. `graph --out FILE` and `adapt NAME --out FILE` write to any path you name: missing folders are created, an existing file is replaced, and a directory is refused. `graph` prints a short summary on stderr; `--verbose` adds every count of what was skipped or could not be placed. See the [workflow graph guide](docs/graph.md) and [adapters](docs/adapters.md). The ADE adapters (`adapt NAME`) are experimental: each is tested on a synthetic fixture written from its tool's format, not yet on a real store.
272
271
 
273
272
  ## More
274
273
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "loopmath"
7
- version = "0.3.0"
7
+ version = "0.3.1"
8
8
  description = "Plan agent workflows, record how they went, and learn from your own Claude Code and Codex logs"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -5,4 +5,4 @@ The command line (`loopmath`, or its alias `loop`) is the interface; see the
5
5
  README and `loopmath --help`.
6
6
  """
7
7
 
8
- __version__ = "0.3.0"
8
+ __version__ = "0.3.1"
@@ -245,11 +245,8 @@ def _add_prices(sub) -> None:
245
245
 
246
246
 
247
247
  def _add_scan(sub) -> None:
248
- p = sub.add_parser(
249
- "scan",
250
- help="explain the implicit scan used by graph and analyze",
251
- description="Scanning is implicit in loopmath graph and loopmath analyze.",
252
- )
248
+ # Hidden: no help= keeps it off `loopmath --help`, and HIDDEN keeps it off the usage line.
249
+ p = sub.add_parser("scan", description="Scanning is implicit in loopmath graph and loopmath analyze.")
253
250
  p.set_defaults(func=_cli_support.scan_verb)
254
251
 
255
252
 
@@ -328,7 +325,7 @@ def adapt_verb(args: argparse.Namespace) -> int:
328
325
  return 1
329
326
  if not isinstance(doc, dict):
330
327
  raise TypeError(f"adapter {args.name!r} emit() must return a dict")
331
- if not doc.get("nodes") and not doc.get("attempts"):
328
+ if not doc.get("nodes") and not doc.get("attempts") and not doc.get("events"): # atrium emits events only
332
329
  where = ", ".join(str(p) for p in selection.stores) if selection.stores else "its default location"
333
330
  print(f"error: adapt {args.name} found no sessions in {where}; nothing written. Point --store at the "
334
331
  f"{args.name} data, or widen --since, --until, --session and --workspace", file=sys.stderr)
@@ -350,12 +347,16 @@ def adapt_verb(args: argparse.Namespace) -> int:
350
347
 
351
348
  def _add_adapt(sub) -> None:
352
349
  p = sub.add_parser(
353
- "adapt", help="emit OCP v0.2 from an ADE store (loopmath ocp migrate converts it to v0.3), or import runs",
350
+ "adapt", help="emit OCP v0.2 from an ADE store (experimental; loopmath ocp migrate converts it to v0.3), "
351
+ "or import runs",
354
352
  description="Emit OCP v0.2 from an ADE store, or import rollouts as OCP v0.3 runs with a run importer: "
355
353
  "`loopmath adapt harness2 RUNS --out DIR` reads a harness2 runs folder, and "
356
354
  "`loopmath adapt harness-runs FILE --out DIR` reads a loopmath.harness-runs/0.1 file "
357
- "(spec/harness-runs.md). Then `loopmath run import DIR --finish` stores them.")
358
- p.add_argument("name", metavar="NAME", help="registered adapter name, or a run importer: harness2, harness-runs")
355
+ "(spec/harness-runs.md). Then `loopmath run import DIR --finish` stores them. "
356
+ "Experimental: the ADE adapters and the harness2 importer, which are tested on synthetic "
357
+ "fixtures written from each tool's format, not yet on real data.")
358
+ p.add_argument("name", metavar="NAME", help="registered adapter name (experimental), or a run importer: "
359
+ "harness2 (experimental), harness-runs")
359
360
  p.add_argument("path", nargs="?", default=None, metavar="PATH",
360
361
  help="the input: the same as --store PATH for an adapter; the runs folder (harness2) or rows file (harness-runs) for a run importer")
361
362
  p.add_argument("--out", default=None, help="write here instead of stdout (any path; missing folders are created; an existing file is replaced). Run importers: a folder, one <run id>.ocp.json per rollout")
@@ -429,9 +430,9 @@ def _add_validate_prices(sub) -> None:
429
430
  def _add_fit(sub) -> None:
430
431
  from .research_defaults import E1A_OBSERVE, SEED
431
432
 
433
+ # Hidden: no help= keeps it off `loopmath --help`, and HIDDEN keeps it off the usage line.
432
434
  research = sub.add_parser(
433
435
  "research",
434
- help="verbs for research data folders that are not part of the package (loopmath research --help)",
435
436
  description="Verbs for research data folders that are not part of the package: sweep run records "
436
437
  "(research fit, research transfer-test; they need the bayes extra) and a session corpus "
437
438
  "(research analyze-e0; it needs the e0 extra). Name a folder with --sweep-dir or --corpus, "
@@ -463,21 +464,38 @@ def _add_fit(sub) -> None:
463
464
 
464
465
  def verify_receipts_verb(args: argparse.Namespace) -> int:
465
466
  """No ledger: check the store's receipts. A ledger path: verify that research receipt ledger."""
467
+ from .output import emit_json
468
+
469
+ schema = "loopmath.verify-receipts/1"
466
470
  if args.ledger is None:
467
471
  from .store.home import Store
468
472
  from .store.verify import verify_lines, verify_store
469
473
 
470
474
  res = verify_store(Store(args.home))
471
- print("\n".join(verify_lines(res)))
472
- return 1 if res["failed"] else 0
475
+ lines = verify_lines(res)
476
+ code = 1 if res["failed"] else 0
477
+ if args.json:
478
+ emit_json(schema, {"ok": not code, **res, "message": "\n".join(lines), **({"exit": code} if code else {})})
479
+ else:
480
+ print("\n".join(lines))
481
+ return code
473
482
  from .receipts import ReceiptViolation, verify_receipts
474
483
 
475
484
  try:
476
485
  result = verify_receipts(args.ledger, args.frozen)
477
486
  except ReceiptViolation as exc:
478
- print(f"FAIL: {exc}")
487
+ if args.json:
488
+ emit_json(schema, {"ok": False, "ledger_path": str(args.ledger), "message": f"FAIL: {exc}",
489
+ "error": str(exc), "exit": 1})
490
+ else:
491
+ print(f"FAIL: {exc}")
479
492
  return 1
480
- print(result.message())
493
+ if args.json:
494
+ emit_json(schema, {"ok": True, "ledger_path": str(args.ledger), "message": result.message(),
495
+ "ledger": {"receipts": result.receipts, "task_arms": result.task_arms,
496
+ "spend_usd": float(result.spend_usd), "prereg": str(result.prereg)}})
497
+ else:
498
+ print(result.message())
481
499
  return 0
482
500
 
483
501
 
@@ -496,11 +514,17 @@ def _add_verify_receipts(sub) -> None:
496
514
  help="with a ledger: its checksum file (default: FROZEN.txt beside the ledger)",
497
515
  )
498
516
  p.add_argument("--home", default=None, metavar="PATH", help="the store (default: $LOOPMATH_HOME or ~/.loopmath)")
517
+ p.add_argument("--json", action="store_true", help="print one JSON object on stdout")
499
518
  p.set_defaults(func=verify_receipts_verb)
500
519
 
501
520
 
502
521
  def main(argv: list[str] | None = None) -> int:
503
- parser = argparse.ArgumentParser(
522
+ from .cli_errors import JsonErrorParser, run_guarded, wants_json
523
+
524
+ raw_args = sys.argv[1:] if argv is None else argv
525
+ JsonErrorParser.json_mode = wants_json(raw_args) # under --json a usage error is a JSON object on stdout too
526
+ JsonErrorParser.argv = list(raw_args)
527
+ parser = JsonErrorParser(
504
528
  prog="loopmath", description="loopmath: which agent workflow to run next, priced from your own logs"
505
529
  )
506
530
  parser.add_argument("--version", action="version", version=f"loopmath {__version__}")
@@ -537,7 +561,6 @@ def main(argv: list[str] | None = None) -> int:
537
561
 
538
562
  sub.metavar = _registry.command_metavar(sub) # hidden commands stay callable, off the usage line
539
563
 
540
- raw_args = sys.argv[1:] if argv is None else argv
541
564
  if raw_args and raw_args[0] == "view":
542
565
  parser.error(
543
566
  "loopmath view is unavailable; use 'loopmath graph --format dot' with "
@@ -550,6 +573,9 @@ def main(argv: list[str] | None = None) -> int:
550
573
  from .ingest.base import use_home
551
574
 
552
575
  use_home(getattr(args, "home", None)) # the caches follow --home as the store does
576
+ if getattr(args, "json", False) is True:
577
+ words = [args.command] + [str(v) for k, v in vars(args).items() if k.endswith("_command") and v]
578
+ return run_guarded(args.func, args, " ".join(words))
553
579
  return args.func(args)
554
580
 
555
581
 
@@ -0,0 +1,121 @@
1
+ """With `--json`, an error is one JSON object on stdout too.
2
+
3
+ A command that fails under `--json` without printing anything on stdout gets one object there,
4
+ `{"schema": "loopmath.error/1", "ok": false, "command": ..., "error": ..., "exit": ...}`, with the same
5
+ exit code and the same message on stderr as before. That covers a usage error from the parser (exit 2),
6
+ a handler that reports its error on stderr only, and an uncaught exception (the traceback still prints).
7
+ A command that printed its own JSON error, as `run import`, `check-change` and `gate` do, is left alone.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import sys
14
+ from typing import Any, Callable
15
+
16
+ from .output import emit_json
17
+
18
+ SCHEMA = "loopmath.error/1"
19
+ _MAX_KEPT = 64_000 # characters of stderr kept for the message
20
+
21
+
22
+ def wants_json(argv: list[str]) -> bool:
23
+ """`--json` among the options, before any `--`."""
24
+ for token in argv:
25
+ if token == "--":
26
+ return False
27
+ if token == "--json":
28
+ return True
29
+ return False
30
+
31
+
32
+ def emit_error(command: str, message: str, code: int, hint: str | None = None) -> None:
33
+ emit_json(SCHEMA, {"ok": False, "command": command, "error": message, **({"hint": hint} if hint else {}),
34
+ "exit": code})
35
+
36
+
37
+ class JsonErrorParser(argparse.ArgumentParser):
38
+ """An ArgumentParser whose usage errors, under --json, also print one JSON object on stdout."""
39
+
40
+ json_mode = False # set by cli.main for each command line
41
+ argv: list[str] = []
42
+
43
+ def error(self, message: str): # type: ignore[override]
44
+ if JsonErrorParser.json_mode:
45
+ self.print_usage(sys.stderr)
46
+ print(f"{self.prog}: error: {message}", file=sys.stderr)
47
+ emit_error(_command(self.prog, JsonErrorParser.argv), message, 2)
48
+ sys.exit(2)
49
+ super().error(message)
50
+
51
+
52
+ def _command(prog: str, argv: list[str]) -> str:
53
+ """The command a usage error belongs to: the parser's words after `loopmath`, else the first word given."""
54
+ words = prog.split()[1:]
55
+ if not words and argv and not argv[0].startswith("-"):
56
+ words = [argv[0]]
57
+ return " ".join(words) or "loopmath"
58
+
59
+
60
+ class _Watch:
61
+ """A pass-through text stream that remembers whether anything was written, and keeps what was."""
62
+
63
+ def __init__(self, inner, keep: bool) -> None:
64
+ self._inner = inner
65
+ self._keep = keep
66
+ self.wrote = False
67
+ self.kept: list[str] = []
68
+ self._size = 0
69
+
70
+ def write(self, text: str) -> int:
71
+ if text:
72
+ self.wrote = True
73
+ if self._keep and self._size < _MAX_KEPT:
74
+ self.kept.append(text)
75
+ self._size += len(text)
76
+ return self._inner.write(text)
77
+
78
+ def __getattr__(self, name: str) -> Any:
79
+ return getattr(self._inner, name)
80
+
81
+
82
+ def _message(stderr_text: str, command: str, code: int) -> tuple[str, str | None]:
83
+ """The error lines on stderr (else its last line), and its `hint:` lines."""
84
+ lines = [line.strip() for line in stderr_text.splitlines() if line.strip()]
85
+ errors = [line.split("error: ", 1)[1] for line in lines if line.startswith("error: ") or ": error: " in line]
86
+ hints = [line[len("hint: "):] for line in lines if line.startswith("hint: ")]
87
+ hint = "; ".join(hints) or None
88
+ if errors:
89
+ return "; ".join(errors), hint
90
+ rest = [line for line in lines if not line.startswith("hint: ")]
91
+ if rest:
92
+ return rest[-1], hint
93
+ return f"loopmath {command} stopped with exit {code}", hint
94
+
95
+
96
+ def run_guarded(func: Callable[[argparse.Namespace], int], args: argparse.Namespace, command: str) -> int:
97
+ """Run a handler under --json: when it fails and stdout stayed empty, print the error object there."""
98
+ out_watch, err_watch = _Watch(sys.stdout, keep=False), _Watch(sys.stderr, keep=True)
99
+ old_out, old_err = sys.stdout, sys.stderr
100
+ sys.stdout, sys.stderr = out_watch, err_watch
101
+ try:
102
+ code = func(args)
103
+ except SystemExit as exc:
104
+ sys.stdout, sys.stderr = old_out, old_err
105
+ code = exc.code if isinstance(exc.code, int) else (0 if exc.code is None else 1)
106
+ if code and not out_watch.wrote:
107
+ text = "".join(err_watch.kept) or (exc.code if isinstance(exc.code, str) else "")
108
+ message, hint = _message(text, command, code)
109
+ emit_error(command, message, code, hint)
110
+ raise
111
+ except Exception as exc:
112
+ sys.stdout, sys.stderr = old_out, old_err
113
+ if not out_watch.wrote:
114
+ emit_error(command, f"{type(exc).__name__}: {exc}", 1)
115
+ raise
116
+ finally:
117
+ sys.stdout, sys.stderr = old_out, old_err
118
+ if code and not out_watch.wrote:
119
+ message, hint = _message("".join(err_watch.kept), command, code)
120
+ emit_error(command, message, code, hint)
121
+ return code
@@ -66,7 +66,9 @@ def _check_args(p: argparse.ArgumentParser) -> None:
66
66
 
67
67
 
68
68
  def _add_compare(sub) -> None:
69
- p = sub.add_parser("check-change", help="paired A/B check of a change: start, status, from-runs")
69
+ p = sub.add_parser("check-change", help="paired A/B check of a change: start, status, from-runs",
70
+ description="A paired A/B check of a change. Start a check, record its pairs, and read its "
71
+ "comparison and verdict.")
70
72
  cs = p.add_subparsers(dest="check_command", required=True, metavar="{start,status,from-runs}")
71
73
 
72
74
  def decision(q: argparse.ArgumentParser) -> None:
@@ -85,6 +87,11 @@ def _add_compare(sub) -> None:
85
87
  "long-lived change)")
86
88
  q.add_argument("--value-usd", type=float, default=None, metavar="USD", help="dollars per point of gain "
87
89
  "(default: the base's cost per accepted result)")
90
+ q.add_argument("--value-stop", action="store_true", default=None, help="stop the check, keeping the base, "
91
+ "when the next pair is worth less than its price (by default the value and the price are "
92
+ "shown at each look and the check goes on to a decision, its cap or its budget)")
93
+ q.add_argument("--min-pairs", type=int, default=None, metavar="K", help="with --value-stop, the scored "
94
+ "pairs before the value may stop the check (default 4; 1 stops from the first pair)")
88
95
 
89
96
  q = cs.add_parser("start", help="start a check: its pairs, each pair's arm order, and the next pair to run")
90
97
  q.add_argument("--base", required=True, metavar="LABEL|PATH", help="the base arm: a label, or its harness tree")
@@ -186,10 +193,10 @@ def _add_planning(sub) -> None:
186
193
  p.add_argument("--brief", action="store_true", help="with --json: a short object for agents (task, reference, "
187
194
  "goal, rescue, choices, message, rec, and page with --html); the stored recommendation keeps everything")
188
195
  p.add_argument("--harness-variants", nargs="?", const=5, type=int, default=None, metavar="N",
189
- help="also price the N harness trees recorded on your runs that the fit saw most (default 5), for the "
190
- "default pick and the goal, against the bare harness label on the same draws")
191
- p.add_argument("--harness", default=None, metavar="PATH|auto", help="also price this harness tree as the first "
192
- "variant (auto: the git working tree of this folder), even when no run recorded it")
196
+ help="experimental: also price the N harness trees recorded on your runs that the fit saw most "
197
+ "(default 5), for the default pick and the goal, against the bare harness label on the same draws")
198
+ p.add_argument("--harness", default=None, metavar="PATH|auto", help="experimental: also price this harness tree "
199
+ "as the first variant (auto: the git working tree of this folder), even when no run recorded it")
193
200
  _common(p, html=True)
194
201
  p.set_defaults(func=_lazy("loopmath.recommend.commands:recommend"))
195
202
 
@@ -445,9 +452,11 @@ def _add_learning(sub) -> None:
445
452
  _common(p)
446
453
  p.set_defaults(func=_lazy("loopmath.share.commands:share"))
447
454
 
448
- p = sub.add_parser("prior", help="the shipped prior bundle: show, build, import-shared, remove-shared")
449
- ps = p.add_subparsers(dest="prior_command", required=True)
450
- q = ps.add_parser("build", help="rebuild the packaged prior bundle from the sweep, E0 and RQ1 inputs")
455
+ p = sub.add_parser("prior", help="the shipped prior bundle: show, import-shared, remove-shared")
456
+ ps = p.add_subparsers(dest="prior_command", required=True, metavar="{show,import-shared,remove-shared}")
457
+ # Hidden, as `plan` is: no help= keeps `build` out of `loopmath prior --help`; it stays callable by name.
458
+ q = ps.add_parser("build", description="Rebuild the packaged prior bundle from the sweep, E0 and RQ1 inputs. "
459
+ "It needs research data folders that do not ship with the package.")
451
460
  q.add_argument("--out", default=None, metavar="DIR", help="where to write the bundle (required)")
452
461
  q.add_argument("--sweep-dir", action="append", default=None, metavar="PATH", help="sweep results, one folder per batch; repeatable (default: LOOPMATH_SWEEP_DIR, one folder)")
453
462
  q.add_argument("--e0-corpus", default=None, metavar="PATH", help="E0 corpus (default: LOOPMATH_E0_CORPUS)")
@@ -483,7 +492,7 @@ def _add_viewing(sub) -> None:
483
492
 
484
493
  p = sub.add_parser("posterior", help="current estimates by level and by workflow graph")
485
494
  p.add_argument("--level", default="all",
486
- choices=["model", "effort", "role", "topology", "type", "repo", "feature", "harness", "all"], help="which level of estimates (default all; harness: the harness labels and their parts)")
495
+ choices=["model", "effort", "role", "topology", "type", "repo", "feature", "harness", "all"], help="which level of estimates (default all; harness: the harness labels and their parts, experimental)")
487
496
  p.add_argument("--head", default=None, metavar="cost|tokens|success|gate|score:NAME", help="which quantity to show")
488
497
  p.add_argument("--target", default=None, metavar="NAME>=X", help="score target for the chance to reach and the cost per accepted result, as for recommend (default: the latest recommendation's target for this type and repo)")
489
498
  p.add_argument("--workflow", default=None, metavar="CFG|FILE.toml", help="also draw this configuration with each piece's estimate: a cfg_ id from loopmath recommend --json or your runs, or FILE.toml with settings")
@@ -546,7 +555,7 @@ def _add_viewing(sub) -> None:
546
555
  q.set_defaults(func=_lazy("loopmath.ocp.commands:migrate"))
547
556
 
548
557
 
549
- HIDDEN = frozenset({"plan", "analyze-e0"}) # callable, but not listed by `loopmath --help`
558
+ HIDDEN = frozenset({"plan", "analyze-e0", "scan", "research"}) # callable, but not listed by `loopmath --help`
550
559
 
551
560
 
552
561
  def command_metavar(sub) -> str:
@@ -555,7 +564,9 @@ def command_metavar(sub) -> str:
555
564
 
556
565
 
557
566
  def _add_gate(sub) -> None:
558
- p = sub.add_parser("gate", help="adopt or reject a harness change: compare base and candidate runs")
567
+ p = sub.add_parser("gate", help="experimental: adopt or reject a harness change: compare base and candidate runs",
568
+ description="Experimental: adopt or reject a harness change, comparing base and candidate "
569
+ "runs, for a loop that adopts changes on its own.")
559
570
  p.add_argument("--base", required=True, metavar="SEL",
560
571
  help="the base arm: digest:sha256:<hex> or sha256:<hex> (the harness record's digest; a prefix of "
561
572
  "one digest will do), a cfg_ configuration id, or arm:<name> or arm=<name>")