loopmath 0.2.4__tar.gz → 0.2.5__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 (380) hide show
  1. {loopmath-0.2.4/src/loopmath.egg-info → loopmath-0.2.5}/PKG-INFO +3 -3
  2. {loopmath-0.2.4 → loopmath-0.2.5}/README.md +2 -2
  3. {loopmath-0.2.4 → loopmath-0.2.5}/pyproject.toml +1 -1
  4. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/__init__.py +1 -1
  5. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/opencode.py +1 -1
  6. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/pi.py +2 -2
  7. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/design.py +9 -0
  8. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/cli_support.py +3 -13
  9. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/html_common.py +1 -1
  10. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ocp/contractv3.py +13 -4
  11. loopmath-0.2.5/src/loopmath/ocp/graph_contract.py +86 -0
  12. loopmath-0.2.5/src/loopmath/ocp/membership.py +69 -0
  13. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ocp/migrate.py +18 -11
  14. loopmath-0.2.5/src/loopmath/report/eligibility.py +45 -0
  15. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/report/html.py +5 -2
  16. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/report/terminal.py +3 -18
  17. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/fitjob.py +5 -1
  18. {loopmath-0.2.4 → loopmath-0.2.5/src/loopmath.egg-info}/PKG-INFO +3 -3
  19. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath.egg-info/SOURCES.txt +4 -0
  20. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_adapter_opencode.py +9 -9
  21. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_adapter_opencode_output_tokens.py +1 -1
  22. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_adapters_pi.py +9 -9
  23. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_analyze_cli.py +61 -4
  24. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_html.py +9 -0
  25. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_home_guard.py +1 -0
  26. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_html.py +16 -0
  27. loopmath-0.2.5/tests/test_report_eligibility.py +40 -0
  28. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_terminal.py +2 -2
  29. {loopmath-0.2.4 → loopmath-0.2.5}/LICENSE +0 -0
  30. {loopmath-0.2.4 → loopmath-0.2.5}/setup.cfg +0 -0
  31. {loopmath-0.2.4 → loopmath-0.2.5}/spec/ocp-v0.2.schema.json +0 -0
  32. {loopmath-0.2.4 → loopmath-0.2.5}/spec/ocp-v0.schema.json +0 -0
  33. {loopmath-0.2.4 → loopmath-0.2.5}/spec/ocp_conformance.py +0 -0
  34. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/__main__.py +0 -0
  35. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapter_host.py +0 -0
  36. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/__init__.py +0 -0
  37. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/atrium.py +0 -0
  38. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/base.py +0 -0
  39. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/bb.py +0 -0
  40. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/bb_support.py +0 -0
  41. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/omp.py +0 -0
  42. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/omp_facts.py +0 -0
  43. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/omp_links.py +0 -0
  44. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/omp_parse.py +0 -0
  45. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/omp_types.py +0 -0
  46. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/opencode_store.py +0 -0
  47. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/orca.py +0 -0
  48. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/otel_genai.py +0 -0
  49. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/otel_genai_graph.py +0 -0
  50. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/otel_genai_parse.py +0 -0
  51. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/otel_genai_types.py +0 -0
  52. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/otel_genai_usage.py +0 -0
  53. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/paseo.py +0 -0
  54. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/pi_support.py +0 -0
  55. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/adapters/registry.py +0 -0
  56. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/__init__.py +0 -0
  57. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/block.py +0 -0
  58. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/check_pymc.py +0 -0
  59. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/commands.py +0 -0
  60. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/compose.py +0 -0
  61. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/features.py +0 -0
  62. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/fit.py +0 -0
  63. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/forest.py +0 -0
  64. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/gaussian.py +0 -0
  65. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/lookahead.py +0 -0
  66. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/outcome.py +0 -0
  67. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/pricing.py +0 -0
  68. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/priors.py +0 -0
  69. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/belief/state.py +0 -0
  70. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/builder/__init__.py +0 -0
  71. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/builder/cli.py +0 -0
  72. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/builder/context.py +0 -0
  73. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/builder/predict.py +0 -0
  74. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/builder/server.py +0 -0
  75. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/cli.py +1 -1
  76. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/cli_graph.py +0 -0
  77. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/cli_registry.py +0 -0
  78. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/e0/__init__.py +0 -0
  79. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/e0/arms.py +0 -0
  80. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/e0/cli_e0.py +0 -0
  81. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/e0/estimate.py +0 -0
  82. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/e0/estimate_rework.py +0 -0
  83. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/e0/figures.py +0 -0
  84. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/e0/io.py +0 -0
  85. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/e0/verb.py +0 -0
  86. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/explain.py +0 -0
  87. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/fit.py +0 -0
  88. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/fit_assembly.py +0 -0
  89. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/fit_bayes.py +0 -0
  90. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/fit_masks.py +0 -0
  91. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/fit_predict.py +0 -0
  92. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/fit_pricing.py +0 -0
  93. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/fit_transfer.py +0 -0
  94. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/gaps.py +0 -0
  95. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/gitwalk.py +0 -0
  96. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/grade.py +0 -0
  97. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/grade_support.py +0 -0
  98. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/__init__.py +0 -0
  99. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/artifact_git.py +0 -0
  100. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/artifact_join.py +0 -0
  101. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/artifact_kinds.py +0 -0
  102. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/artifacts.py +0 -0
  103. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/bashparse.py +0 -0
  104. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/bashread_ops.py +0 -0
  105. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/bashwrite_ops.py +0 -0
  106. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/bashwrites.py +0 -0
  107. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/cache.py +0 -0
  108. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/codexio.py +0 -0
  109. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/codexio_parse.py +0 -0
  110. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/codexio_scan.py +0 -0
  111. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/dataset.py +0 -0
  112. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/dataset_build.py +0 -0
  113. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/dataset_core.py +0 -0
  114. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/dataset_edges.py +0 -0
  115. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/dataset_nodes.py +0 -0
  116. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/extract.py +0 -0
  117. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/html_costcurve.py +0 -0
  118. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/html_css.py +0 -0
  119. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/html_data.py +0 -0
  120. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/html_force.py +0 -0
  121. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/html_render.py +0 -0
  122. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/html_swimlanes.py +0 -0
  123. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler.py +0 -0
  124. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_cli.py +0 -0
  125. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_common.py +0 -0
  126. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_parse.py +0 -0
  127. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_pricing.py +0 -0
  128. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_prompt.py +0 -0
  129. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_prompt_build.py +0 -0
  130. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_prompt_dispatch.py +0 -0
  131. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_prompt_evidence.py +0 -0
  132. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_prompt_node.py +0 -0
  133. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_prompt_records.py +0 -0
  134. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_prompt_result.py +0 -0
  135. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_prompt_session.py +0 -0
  136. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_prompt_tools.py +0 -0
  137. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_prompt_validation.py +0 -0
  138. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_prompt_workflow.py +0 -0
  139. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_report.py +0 -0
  140. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labeler_score.py +0 -0
  141. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/labels.py +0 -0
  142. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/launch.py +0 -0
  143. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/launch_origin.py +0 -0
  144. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/ocp.py +0 -0
  145. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/ocp_emit.py +0 -0
  146. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/ocp_support.py +0 -0
  147. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/render.py +0 -0
  148. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/runfile.py +0 -0
  149. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/runfile_export.py +0 -0
  150. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/runfile_finish.py +0 -0
  151. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/scan.py +0 -0
  152. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/schema.py +0 -0
  153. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/graph/token_completeness.py +0 -0
  154. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ingest/__init__.py +0 -0
  155. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ingest/base.py +0 -0
  156. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ingest/claude_code.py +0 -0
  157. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ingest/claude_code_support.py +0 -0
  158. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ingest/codex.py +0 -0
  159. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ingest/codex_support.py +0 -0
  160. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ingest/ocp.py +0 -0
  161. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ingest/ocp_analysis.py +0 -0
  162. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ingest/ocp_artifact.py +0 -0
  163. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ingest/ocp_common.py +0 -0
  164. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ingest/ocp_convert.py +0 -0
  165. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ingest/ocp_projection.py +0 -0
  166. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/logmatch/__init__.py +0 -0
  167. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/logmatch/artifacts.py +0 -0
  168. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/logmatch/clip.py +0 -0
  169. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/logmatch/costs.py +0 -0
  170. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/logmatch/match.py +0 -0
  171. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/logmatch/settle.py +0 -0
  172. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/logmatch/tariff.py +0 -0
  173. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ocp/__init__.py +0 -0
  174. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ocp/canonical.py +0 -0
  175. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ocp/commands.py +0 -0
  176. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ocp/conformance.py +0 -0
  177. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ocp/emit.py +0 -0
  178. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ocp/schema/ocp-v0.2.schema.json +0 -0
  179. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ocp/schema/ocp-v0.3.schema.json +0 -0
  180. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ocp/schema/ocp-v0.schema.json +0 -0
  181. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/ocp/version.py +0 -0
  182. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/onboard/__init__.py +0 -0
  183. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/onboard/commands.py +0 -0
  184. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/onboard/history.py +0 -0
  185. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/onboard/label.py +0 -0
  186. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/onboard/usual.py +0 -0
  187. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/output.py +0 -0
  188. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/pool.py +0 -0
  189. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/price.py +0 -0
  190. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/price_validation.py +0 -0
  191. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/prices.toml +0 -0
  192. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/__init__.py +0 -0
  193. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/benchmarks.py +0 -0
  194. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/benchmarks.toml +0 -0
  195. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/build.py +0 -0
  196. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/bundle/README.md +0 -0
  197. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/bundle/e0.jsonl.gz +0 -0
  198. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/bundle/lanes.jsonl.gz +0 -0
  199. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/bundle/manifest.json +0 -0
  200. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/bundle/rq1.jsonl.gz +0 -0
  201. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/bundle/sweep.jsonl.gz +0 -0
  202. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/commands.py +0 -0
  203. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/e0.py +0 -0
  204. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/history.py +0 -0
  205. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/lanes.py +0 -0
  206. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/ocpdoc.py +0 -0
  207. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/reduce.py +0 -0
  208. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/registry.py +0 -0
  209. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/rq1.py +0 -0
  210. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/show.py +0 -0
  211. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/sweep.py +0 -0
  212. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/priors/validate.py +0 -0
  213. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/receipts.py +0 -0
  214. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/recommend/__init__.py +0 -0
  215. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/recommend/backlog.py +0 -0
  216. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/recommend/commands.py +0 -0
  217. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/recommend/curve.py +0 -0
  218. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/recommend/engine.py +0 -0
  219. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/recommend/gain.py +0 -0
  220. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/recommend/message.py +0 -0
  221. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/recommend/receipts.py +0 -0
  222. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/recommend/search.py +0 -0
  223. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/recommend/stats.py +0 -0
  224. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/recommend/storeread.py +0 -0
  225. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/report/__init__.py +0 -0
  226. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/report/terminal_exclusions.py +0 -0
  227. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/report/terminal_format.py +0 -0
  228. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/report/terminal_read.py +0 -0
  229. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/research_defaults.py +0 -0
  230. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/research_paths.py +0 -0
  231. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/scoring.py +0 -0
  232. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/scoring_comparisons.py +0 -0
  233. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/scoring_predictive.py +0 -0
  234. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/share/__init__.py +0 -0
  235. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/share/commands.py +0 -0
  236. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/share/export.py +0 -0
  237. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/share/import_.py +0 -0
  238. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/skill/__init__.py +0 -0
  239. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/skill/commands.py +0 -0
  240. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/skill/doctor.py +0 -0
  241. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/skill/install.py +0 -0
  242. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/skill/skills/loopmath/SKILL.md +0 -0
  243. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/skill/skills/loopmath-import-runs/SKILL.md +0 -0
  244. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/skill/skills/loopmath-onboard/SKILL.md +0 -0
  245. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/skill/skills/loopmath-plan-task/SKILL.md +0 -0
  246. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/skill/skills/loopmath-record-run/SKILL.md +0 -0
  247. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/skill/skills/loopmath-update-fit/SKILL.md +0 -0
  248. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/skill/skills/reference.md +0 -0
  249. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/__init__.py +0 -0
  250. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/budget.py +0 -0
  251. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/commands.py +0 -0
  252. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/config.py +0 -0
  253. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/finish.py +0 -0
  254. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/home.py +0 -0
  255. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/ids.py +0 -0
  256. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/lock.py +0 -0
  257. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/record.py +0 -0
  258. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/report.py +0 -0
  259. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/run_names.py +0 -0
  260. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/runs.py +0 -0
  261. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/status.py +0 -0
  262. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/store/verify.py +0 -0
  263. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/surface.py +0 -0
  264. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/surface_bootstrap.py +0 -0
  265. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/surface_estimation.py +0 -0
  266. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/surface_frame.py +0 -0
  267. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/surface_presentation.py +0 -0
  268. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/surface_support.py +0 -0
  269. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/taskmodel.py +0 -0
  270. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/types.py +0 -0
  271. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/__init__.py +0 -0
  272. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/README.md +0 -0
  273. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/base.css +0 -0
  274. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/builder.css +0 -0
  275. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/builder.html +0 -0
  276. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/builder.js +0 -0
  277. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/common.js +0 -0
  278. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/estimates.css +0 -0
  279. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/estimates.js +0 -0
  280. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/graph.css +0 -0
  281. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/graph.js +0 -0
  282. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/plans.css +0 -0
  283. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/plans.js +0 -0
  284. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/results.css +0 -0
  285. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/results.js +0 -0
  286. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/runs.js +0 -0
  287. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/viz.css +0 -0
  288. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/assets/viz.js +0 -0
  289. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/common.py +0 -0
  290. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/plans.py +0 -0
  291. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/posterior.py +0 -0
  292. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/views/runs.py +0 -0
  293. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/__init__.py +0 -0
  294. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/candidates.py +0 -0
  295. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/catalog/best_of_n.toml +0 -0
  296. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/catalog/implement_review.toml +0 -0
  297. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/catalog/plan_implement.toml +0 -0
  298. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/catalog/plan_implement_review.toml +0 -0
  299. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/catalog/solo.toml +0 -0
  300. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/catalog/swarm.toml +0 -0
  301. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/commands.py +0 -0
  302. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/diff.py +0 -0
  303. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/format.py +0 -0
  304. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/graphview.py +0 -0
  305. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/ids.py +0 -0
  306. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/infer.py +0 -0
  307. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/models.py +0 -0
  308. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/ocp.py +0 -0
  309. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath/workflows/shapes.py +0 -0
  310. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath.egg-info/dependency_links.txt +0 -0
  311. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath.egg-info/entry_points.txt +0 -0
  312. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath.egg-info/requires.txt +0 -0
  313. {loopmath-0.2.4 → loopmath-0.2.5}/src/loopmath.egg-info/top_level.txt +0 -0
  314. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_adapter_atrium.py +0 -0
  315. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_adapter_bb.py +0 -0
  316. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_adapter_omp.py +0 -0
  317. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_adapter_orca.py +0 -0
  318. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_adapter_otel_genai.py +0 -0
  319. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_adapter_paseo.py +0 -0
  320. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_adapter_services.py +0 -0
  321. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_adapters.py +0 -0
  322. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_arms.py +0 -0
  323. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_cli_graph.py +0 -0
  324. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_cli_help_lists.py +0 -0
  325. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_cli_labeler.py +0 -0
  326. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_cli_main_module.py +0 -0
  327. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_cli_plan_hidden.py +0 -0
  328. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_cli_prices.py +0 -0
  329. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_cli_startup.py +0 -0
  330. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_color_proof.py +0 -0
  331. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_duplicate_guards.py +0 -0
  332. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_estimate.py +0 -0
  333. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_explain.py +0 -0
  334. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_fit.py +0 -0
  335. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_gaps.py +0 -0
  336. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_gitwalk.py +0 -0
  337. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_grade.py +0 -0
  338. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_artifact_recording.py +0 -0
  339. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_artifacts.py +0 -0
  340. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_bashwrites.py +0 -0
  341. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_cache.py +0 -0
  342. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_codexio.py +0 -0
  343. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_dataset.py +0 -0
  344. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_extract.py +0 -0
  345. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_glue.py +0 -0
  346. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_labeler.py +0 -0
  347. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_labeler_evidence_matrix.py +0 -0
  348. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_launch.py +0 -0
  349. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_ocp.py +0 -0
  350. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_render.py +0 -0
  351. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_runfile.py +0 -0
  352. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_graph_schema.py +0 -0
  353. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_ingest.py +0 -0
  354. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_ingest_claude_code.py +0 -0
  355. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_ingest_codex.py +0 -0
  356. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_ingest_ocp.py +0 -0
  357. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_io.py +0 -0
  358. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_not_run_summary.py +0 -0
  359. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_ocp_conformance.py +0 -0
  360. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_ocp_golden.py +0 -0
  361. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_ocp_open_enums.py +0 -0
  362. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_ocp_versioning.py +0 -0
  363. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_output_html_path.py +0 -0
  364. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_packaging_metadata.py +0 -0
  365. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_packaging_smoke.py +0 -0
  366. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_pool.py +0 -0
  367. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_preview_script.py +0 -0
  368. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_price.py +0 -0
  369. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_receipts.py +0 -0
  370. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_release_paths.py +0 -0
  371. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_research_cli.py +0 -0
  372. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_runfile_privacy_n1b.py +0 -0
  373. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_scoring.py +0 -0
  374. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_split_rebindings.py +0 -0
  375. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_starting_prior.py +0 -0
  376. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_surface.py +0 -0
  377. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_taskmodel_features.py +0 -0
  378. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_types_control.py +0 -0
  379. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_types_piece_prediction.py +0 -0
  380. {loopmath-0.2.4 → loopmath-0.2.5}/tests/test_v01_scaffold.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loopmath
3
- Version: 0.2.4
3
+ Version: 0.2.5
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
@@ -241,7 +241,7 @@ The store is `~/.loopmath`. Set `LOOPMATH_HOME` or pass `--home` to put it elsew
241
241
  - signals, receipts and fits;
242
242
  - your config and your workflows;
243
243
  - the views you write;
244
- - the caches: the log parse cache, and the folder where `fit --full` compiles its model (when that compile fails, it also leaves the C code it tried in the system temp folder and prints where).
244
+ - the caches: the log parse cache, and the folder where `fit --full` compiles its model (when that compile fails, it also leaves the C code it tried in a `pytensor_compilation_error_*` file in the system temp folder and prints where).
245
245
 
246
246
  Do not edit the store by hand; use the commands. `loopmath verify-receipts` checks the receipts in your store; give a file to verify a research receipt ledger instead.
247
247
 
@@ -260,7 +260,7 @@ loopmath analyze # cost per accepted run from the last 14 days o
260
260
  loopmath graph --workspace NAME --format html --out graph.html
261
261
  ```
262
262
 
263
- `analyze` reads `~/.claude/projects` and `~/.codex/sessions`. It grades the outcome evidence it finds, prices known models, and prints cost per accepted run with an 80% band. It also lists what it could not use and why; when no workflow configuration has enough runs to compare, it says how many runs it read and what kept each out, and what to try (`--min-n`, `--grading`). `loopmath analyze --json` prints the same as one JSON object, and `--home PATH` keeps its parse cache in that store.
263
+ `analyze` reads `~/.claude/projects` and `~/.codex/sessions`. It grades the outcome evidence it finds, prices known models, and prints cost per accepted run with an 80% band. It also lists what it could not use and why; when no workflow configuration is eligible to compare, it names the largest comparison exclusion, its count, and what to try. `loopmath analyze --json` prints the same as one JSON object, and `--home PATH` keeps its parse cache in that store.
264
264
 
265
265
  `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
266
 
@@ -203,7 +203,7 @@ The store is `~/.loopmath`. Set `LOOPMATH_HOME` or pass `--home` to put it elsew
203
203
  - signals, receipts and fits;
204
204
  - your config and your workflows;
205
205
  - the views you write;
206
- - the caches: the log parse cache, and the folder where `fit --full` compiles its model (when that compile fails, it also leaves the C code it tried in the system temp folder and prints where).
206
+ - the caches: the log parse cache, and the folder where `fit --full` compiles its model (when that compile fails, it also leaves the C code it tried in a `pytensor_compilation_error_*` file in the system temp folder and prints where).
207
207
 
208
208
  Do not edit the store by hand; use the commands. `loopmath verify-receipts` checks the receipts in your store; give a file to verify a research receipt ledger instead.
209
209
 
@@ -222,7 +222,7 @@ loopmath analyze # cost per accepted run from the last 14 days o
222
222
  loopmath graph --workspace NAME --format html --out graph.html
223
223
  ```
224
224
 
225
- `analyze` reads `~/.claude/projects` and `~/.codex/sessions`. It grades the outcome evidence it finds, prices known models, and prints cost per accepted run with an 80% band. It also lists what it could not use and why; when no workflow configuration has enough runs to compare, it says how many runs it read and what kept each out, and what to try (`--min-n`, `--grading`). `loopmath analyze --json` prints the same as one JSON object, and `--home PATH` keeps its parse cache in that store.
225
+ `analyze` reads `~/.claude/projects` and `~/.codex/sessions`. It grades the outcome evidence it finds, prices known models, and prints cost per accepted run with an 80% band. It also lists what it could not use and why; when no workflow configuration is eligible to compare, it names the largest comparison exclusion, its count, and what to try. `loopmath analyze --json` prints the same as one JSON object, and `--home PATH` keeps its parse cache in that store.
226
226
 
227
227
  `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.
228
228
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "loopmath"
7
- version = "0.2.4"
7
+ version = "0.2.5"
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.2.4"
8
+ __version__ = "0.2.5"
@@ -103,7 +103,7 @@ _sys.modules[__name__].__class__ = _OpenCodeModule
103
103
  del _ModuleType, _OpenCodeModule, _opencode_store, _sys
104
104
 
105
105
 
106
- _EXT = "dev.dagr.adapter.opencode"
106
+ _EXT = "dev.loopmath.adapter.opencode"
107
107
 
108
108
  def _session_created(session: _SessionSource) -> datetime | None:
109
109
  return _timestamp(_as_mapping(session.info.get("time")).get("created"))
@@ -2,7 +2,7 @@
2
2
 
3
3
  One Pi session is one OCP node with one attempt. Usage-bearing entries are
4
4
  measured requests: their token and USD fields are summed into the attempt's
5
- core cost record, while ``dev.dagr.adapter.pi.usage_records`` retains the exact
5
+ core cost record, while ``dev.loopmath.adapter.pi.usage_records`` retains the exact
6
6
  per-entry breakdown that OCP v0.2 cannot express. Model labels on assistant
7
7
  messages are verified; thinking effort is reported by Pi's explicit
8
8
  ``thinking_level_change`` entries.
@@ -57,7 +57,7 @@ from .registry import register
57
57
 
58
58
 
59
59
  _DEFAULT_STORE = Path.home() / ".pi" / "agent" / "sessions"
60
- _EXT = "dev.dagr.adapter.pi"
60
+ _EXT = "dev.loopmath.adapter.pi"
61
61
 
62
62
 
63
63
  class _PiModule(_ModuleType):
@@ -741,10 +741,19 @@ def parse_run(doc: dict, *, now: datetime | None = None, rule: AcceptanceRule |
741
741
 
742
742
  nodes = {n.get("id"): n for n in doc.get("nodes") or [] if isinstance(n, dict)}
743
743
  node_vertex = {i: n.get("vertex") for i, n in nodes.items()}
744
+ from ..ocp.membership import imported_node_map
745
+
746
+ imported_vertices = imported_node_map(doc)
744
747
  attempts: list[AttemptObs] = []
745
748
  by_piece_round: dict[tuple[str, int], list[dict]] = {}
746
749
  for a in doc.get("attempts") or []:
747
750
  piece = a.get("vertex") or node_vertex.get(a.get("node"))
751
+ node = nodes.get(a.get("node"))
752
+ if (piece not in st.settings and node is not None and "vertex" not in a and "vertex" not in node
753
+ and _check_name(a, node) is None):
754
+ mapped = imported_vertices.get(a.get("node"))
755
+ if isinstance(mapped, str) and mapped in st.settings:
756
+ piece = mapped
748
757
  if piece not in st.settings:
749
758
  name = _check_name(a, nodes.get(a.get("node")) or {})
750
759
  if name is not None:
@@ -100,19 +100,9 @@ def _extremes_ratio(df, result, price_mod) -> dict:
100
100
  table = result.get("table")
101
101
  n_configs = 0 if table is None else len(table)
102
102
  if n_configs < 2:
103
- if n_configs == 0:
104
- return {
105
- "unavailable": (
106
- "no workflow configuration met the minimum run count, so there "
107
- "is nothing to compare"
108
- )
109
- }
110
- return {
111
- "unavailable": (
112
- "only one workflow configuration met the minimum run count, so "
113
- "there is nothing to compare"
114
- )
115
- }
103
+ from .report.eligibility import unavailable_lines
104
+
105
+ return {"unavailable": "; ".join(unavailable_lines(result, n_configs))}
116
106
  ordered = table.sort_values("cost_per_accepted")
117
107
  cheap = ordered.iloc[0]["arm"]
118
108
  dear = ordered.iloc[-1]["arm"]
@@ -86,7 +86,7 @@ const App = (() => {
86
86
  const small = Number(v.toFixed(digits));
87
87
  if (small >= 10 ** (1 - digits)) digits -= 1; // 0.00096 rounds up to $0.001, still one digit
88
88
  if (small < 0.01) return '$' + small.toFixed(digits);
89
- v = small; // 0.0096 rounds up to a cent
89
+ return '$' + small.toFixed(2); // 0.0096 rounds up to $0.01
90
90
  }
91
91
  return '$' + v.toFixed(v < 0.1 ? 3 : 2);
92
92
  },
@@ -31,9 +31,9 @@ Mapping summary (contract v3 -> OCP 0.1):
31
31
  task.policy -> node.ext["dev.dagr.policy"] (typed, inert)
32
32
  attempt.locator -> dropped (volatile runtime address)
33
33
  attempt.liveness -> dropped (live-view data, not telemetry)
34
- costs -> ABSENT: contract v3 carries no token usage.
35
- This is the known gap; a v3 amendment or the
36
- orchestrator plugin must supply attempt.cost.
34
+ graph export ext -> per-attempt costs and full model/harness/effort,
35
+ joined by task and attempt IDs. Plain contracts
36
+ without this telemetry keep costs absent.
37
37
  """
38
38
 
39
39
  from __future__ import annotations
@@ -146,6 +146,9 @@ def convert(doc):
146
146
  raise ValueError("input is not a dagr contract run document")
147
147
  # The contract's own version key is 'dagr' (the pre-rename product name).
148
148
  contract_version = doc.get("dagr", 1)
149
+ from .graph_contract import telemetry
150
+
151
+ graph_records = telemetry(doc)
149
152
 
150
153
  run_in = doc["run"]
151
154
  run = {"id": run_in.get("id", "unknown-run")}
@@ -159,6 +162,9 @@ def convert(doc):
159
162
  "source_contract": f"dagr/{contract_version}",
160
163
  }
161
164
  put(producer, "emitted_at", doc.get("generated_at"))
165
+ if graph_records is not None:
166
+ producer["capabilities"] = {"groups": True, "events": True, "edges_dep": True,
167
+ "cost_usd": True, "cost_tokens": True, "outcome_evidence": True}
162
168
 
163
169
  groups = []
164
170
  for prj in doc.get("projects", []) or []:
@@ -190,7 +196,10 @@ def convert(doc):
190
196
  edges.append({"from": src, "to": tid, "kind": "dep"})
191
197
 
192
198
  for att in task.get("attempts", []) or []:
193
- attempts.append(convert_attempt(tid, att))
199
+ converted = convert_attempt(tid, att)
200
+ if graph_records is not None:
201
+ converted.update(graph_records[tid, converted["id"]])
202
+ attempts.append(converted)
194
203
 
195
204
  events = []
196
205
  for ev in doc.get("events", []) or []:
@@ -0,0 +1,86 @@
1
+ """Read the per-attempt telemetry carried by loopmath's graph contract export."""
2
+ from __future__ import annotations
3
+
4
+ import copy
5
+ import math
6
+
7
+ KEYS = ("dev.loopmath.graph", "dev.dagr.graph")
8
+ TOKEN_FIELDS = ("input_tokens", "cached_input_tokens", "cache_creation_tokens", "cache_creation_5m_tokens",
9
+ "cache_creation_1h_tokens", "output_tokens", "reasoning_tokens", "requests")
10
+
11
+
12
+ def extension(obj: dict) -> dict | None:
13
+ ext = obj.get("ext") or {}
14
+ if not isinstance(ext, dict):
15
+ return None
16
+ found = [ext[key] for key in KEYS if key in ext]
17
+ if not found:
18
+ return None
19
+ if any(not isinstance(value, dict) for value in found) or any(value != found[0] for value in found[1:]):
20
+ raise ValueError("graph extension is malformed or has conflicting namespaces")
21
+ return found[0]
22
+
23
+
24
+ def _cost(value: object) -> dict:
25
+ if not isinstance(value, dict):
26
+ raise ValueError("graph cost must be an object")
27
+ for key in (*TOKEN_FIELDS, "usd"):
28
+ if key not in value:
29
+ continue
30
+ number = value[key]
31
+ if (isinstance(number, bool) or not isinstance(number, (int, float)) or not math.isfinite(number)
32
+ or number < 0 or (key != "usd" and not isinstance(number, int))):
33
+ raise ValueError(f"graph cost {key} must be a finite nonnegative {'number' if key == 'usd' else 'integer'}")
34
+ for key, kind in (("basis", str), ("ext", dict), ("tariff", dict)):
35
+ if key in value and not isinstance(value[key], kind):
36
+ raise ValueError(f"graph cost {key} has the wrong type")
37
+ buckets = ("cache_creation_5m_tokens", "cache_creation_1h_tokens")
38
+ if all(key in value for key in (*buckets, "cache_creation_tokens")):
39
+ if sum(value[key] for key in buckets) != value["cache_creation_tokens"]:
40
+ raise ValueError("graph cost cache retention buckets disagree with their total")
41
+ return copy.deepcopy(value)
42
+
43
+
44
+ def telemetry(doc: dict) -> dict[tuple[str, str], dict] | None:
45
+ """Return exact task/attempt records; None leaves an ordinary contract unchanged.
46
+
47
+ Identical duplicate representations are harmless. Conflicting or dangling
48
+ references are errors, never a reason to choose or allocate an amount.
49
+ """
50
+ graph = extension(doc)
51
+ if graph is None:
52
+ return None
53
+ records = {}
54
+ for task in doc.get("tasks") or []:
55
+ tid = task.get("id", "?")
56
+ attempts = task.get("attempts") or []
57
+ meta = extension(task) or {}
58
+ settings = {key: meta[key] for key in ("model", "harness", "effort") if key in meta and meta[key] is not None}
59
+ if settings and len(attempts) != 1:
60
+ raise ValueError(f"graph task {tid!r} has ambiguous task-level model metadata")
61
+ if "model" in settings and (not isinstance(settings["model"], dict)
62
+ or not isinstance(settings["model"].get("raw"), str)):
63
+ raise ValueError("graph model metadata must be a model reference")
64
+ for key in ("harness", "effort"):
65
+ if key in settings and not isinstance(settings[key], str):
66
+ raise ValueError(f"graph {key} metadata must be a string")
67
+ for attempt in attempts:
68
+ aid = attempt.get("id", f"{tid}.a{attempt.get('n', '?')}")
69
+ if not isinstance(tid, str) or not isinstance(aid, str) or (tid, aid) in records:
70
+ raise ValueError("graph task/attempt identity is invalid or duplicated")
71
+ records[tid, aid] = copy.deepcopy(settings)
72
+ costs = graph.get("costs", [])
73
+ if not isinstance(costs, list):
74
+ raise ValueError("graph costs must be an array")
75
+ for row in costs:
76
+ if not isinstance(row, dict) or not isinstance(row.get("task"), str) or not isinstance(row.get("attempt"), str):
77
+ raise ValueError("graph cost needs task and attempt identities")
78
+ pair = (row["task"], row["attempt"])
79
+ if pair not in records:
80
+ raise ValueError(f"graph cost references an unknown task/attempt pair: {pair!r}")
81
+ cost = _cost(row.get("cost"))
82
+ old = records[pair].get("cost")
83
+ if old is not None and old != cost:
84
+ raise ValueError(f"graph cost records conflict for {pair!r}")
85
+ records[pair]["cost"] = cost
86
+ return records
@@ -0,0 +1,69 @@
1
+ """Read saved import membership without guessing roles or repairing explicit references."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any
5
+
6
+ _ADAPTERS = {"pi", "opencode", "omp", "otel-genai", "paseo", "orca", "bb", "atrium"}
7
+ _MIGRATED_FROM = {"0.1", "0.2", "dagr/1", "dagr/2", "dagr/3"}
8
+ _COST_FIELDS = ("usd", "input_tokens", "cached_input_tokens", "cache_creation_tokens", "output_tokens")
9
+
10
+
11
+ def imported_node_map(doc: dict) -> dict:
12
+ """Saved node map only for recognized migration or adapter/graph provenance."""
13
+ run = doc.get("run") or {}
14
+ ext = run.get("ext") or {}
15
+ marker = ext.get("dev.loopmath.migration") if isinstance(ext, dict) else None
16
+ migrated = (isinstance(marker, dict) and isinstance(marker.get("from"), str)
17
+ and marker["from"] in _MIGRATED_FROM)
18
+ producer = doc.get("producer") or {}
19
+ name = producer.get("name") if isinstance(producer, dict) else None
20
+ adapter = isinstance(name, str) and name in {
21
+ f"{prefix}/adapter-{a}" for prefix in ("loopmath", "dagr") for a in _ADAPTERS}
22
+ top_ext = doc.get("ext") or {}
23
+ graph = name in ("loopmath", "dagr") and isinstance(top_ext, dict) and any(
24
+ isinstance(top_ext.get(key), dict) for key in ("dev.loopmath.graph", "dev.dagr.graph"))
25
+ config = run.get("configuration") or {}
26
+ mapping = config.get("node_vertex") if isinstance(config, dict) else None
27
+ return mapping if (migrated or adapter or graph) and isinstance(mapping, dict) else {}
28
+
29
+
30
+ def model_work(attempt: dict) -> bool:
31
+ """The membership counterpart of the fitter's costless, model-free check rule."""
32
+ setting = attempt.get("setting") or {}
33
+ if attempt.get("model") or (isinstance(setting, dict) and setting.get("model")):
34
+ return True
35
+ cost = attempt.get("cost") or {}
36
+ return isinstance(cost, dict) and any(
37
+ isinstance(cost.get(key), (int, float)) and cost[key] > 0 for key in _COST_FIELDS)
38
+
39
+
40
+ def materialize(doc: dict[str, Any]) -> None:
41
+ """Fill absent vertices from a validated saved map, on the migration's private copy."""
42
+ mapping = imported_node_map(doc)
43
+ if not mapping:
44
+ return
45
+ config = (doc.get("run") or {}).get("configuration") or {}
46
+ workflow = config.get("workflow") or {}
47
+ settings = config.get("settings") or {}
48
+ if not isinstance(workflow, dict) or not isinstance(settings, dict):
49
+ return
50
+ pieces = {p.get("id") for p in workflow.get("pieces") or [] if isinstance(p, dict) and isinstance(p.get("id"), str)}
51
+ by_node: dict[str, list[dict]] = {}
52
+ for attempt in doc.get("attempts") if isinstance(doc.get("attempts"), list) else []:
53
+ if isinstance(attempt, dict) and isinstance(attempt.get("node"), str):
54
+ by_node.setdefault(attempt["node"], []).append(attempt)
55
+ for node in doc.get("nodes") if isinstance(doc.get("nodes"), list) else []:
56
+ if not isinstance(node, dict) or "vertex" in node:
57
+ continue
58
+ nid = node.get("id")
59
+ piece = mapping.get(nid) if isinstance(nid, str) else None
60
+ if not isinstance(piece, str) or piece not in pieces or piece not in settings:
61
+ continue
62
+ attempts = by_node.get(nid, [])
63
+ # A shared node must not give a model-free check (or an explicit empty
64
+ # vertex) membership indirectly through node.vertex.
65
+ eligible = [a for a in attempts if "vertex" not in a and model_work(a)]
66
+ if eligible and len(eligible) == len(attempts):
67
+ node["vertex"] = piece
68
+ for attempt in eligible:
69
+ attempt["vertex"] = piece
@@ -11,7 +11,8 @@ only when absent:
11
11
  - `run.task` from `run.labels` (`task`, `type`, `repo`) when any is present:
12
12
  `id` from the `task` label, else the run id; `labeled_by` is
13
13
  `{how: inferred, tier: heuristic}`;
14
- - `node.vertex` from its attempts' `role.value` when they agree on one value;
14
+ - absent model-attempt vertices from the configuration's saved node mapping,
15
+ then `node.vertex` from unanimous `role.value` when still absent;
15
16
  - `run.provenance = {kind: logged, chooser: habit}`;
16
17
  - `run.configuration = {source: habit}`, plus the inferred workflow and
17
18
  settings when `infer` returns one (lane 04's `loopmath.workflows.infer`),
@@ -106,6 +107,22 @@ def migrate_doc(doc: dict[str, Any], *, infer: Infer | None = infer_configuratio
106
107
  task["labeled_by"] = {"how": "inferred", "tier": "heuristic"}
107
108
  run["task"] = task
108
109
 
110
+ run.setdefault("provenance", {"kind": "logged", "chooser": "habit"})
111
+ if "configuration" not in run:
112
+ inferred = infer(source if origin else out) if infer is not None else None
113
+ if inferred is not None:
114
+ configuration, confidence = inferred
115
+ configuration = dict(configuration, source="habit")
116
+ configuration.setdefault("ext", {})[INFERRED_KEY] = {"confidence": confidence, "tier": "heuristic"}
117
+ run["configuration"] = configuration
118
+ else:
119
+ run["configuration"] = {"source": "habit"}
120
+
121
+ # The exact inference map takes precedence over a role word generated
122
+ # below, while vertices explicitly present in the input remain untouched.
123
+ from .membership import materialize
124
+
125
+ materialize(out)
109
126
  roles: dict[str, set[str]] = {}
110
127
  for attempt in _list(out.get("attempts")):
111
128
  if not isinstance(attempt, dict):
@@ -119,14 +136,4 @@ def migrate_doc(doc: dict[str, Any], *, infer: Infer | None = infer_configuratio
119
136
  if isinstance(nid, str) and "vertex" not in node and len(roles.get(nid, ())) == 1:
120
137
  node["vertex"] = next(iter(roles[nid]))
121
138
 
122
- run.setdefault("provenance", {"kind": "logged", "chooser": "habit"})
123
- if "configuration" not in run:
124
- inferred = infer(source if origin else out) if infer is not None else None
125
- if inferred is not None:
126
- configuration, confidence = inferred
127
- configuration = dict(configuration, source="habit")
128
- configuration.setdefault("ext", {})[INFERRED_KEY] = {"confidence": confidence, "tier": "heuristic"}
129
- run["configuration"] = configuration
130
- else:
131
- run["configuration"] = {"source": "habit"}
132
139
  return out
@@ -0,0 +1,45 @@
1
+ """Explain comparison eligibility using the surface's existing exclusion counts."""
2
+ from collections import Counter
3
+
4
+
5
+ COMPARISON = (
6
+ "configuration below min_overlap_cells",
7
+ "run's task-mix cell not shared with another configuration",
8
+ "configuration below min_n",
9
+ )
10
+ EARLY = ("unknown acceptance", "no usable cost value", "no model label")
11
+
12
+
13
+ def unavailable_lines(surface: dict, n_configs: int = 0) -> list[str]:
14
+ if n_configs == 1:
15
+ return ["only one workflow configuration is eligible, so there is nothing to compare"]
16
+ counts = Counter()
17
+ for item in surface.get("exclusions") or []:
18
+ counts[item.get("reason")] += max(0, int(item.get("n") or 0))
19
+ candidates = sum(counts[r] for r in COMPARISON) + int(surface.get("n_rows_used") or 0)
20
+ choices = [r for r in COMPARISON if counts[r] > 0]
21
+ population = f"{candidates:,} runs with known outcomes, usable cost and model labels"
22
+ if not choices:
23
+ choices = [r for r in EARLY if counts[r] > 0]
24
+ population = f"{int(surface.get('n_rows') or sum(counts.values())):,} runs read"
25
+ if not choices:
26
+ return ["no workflow configuration is eligible, so there is nothing to compare"]
27
+ # A tie follows the fixed presentation order above, never input ordering.
28
+ reason = max(choices, key=counts.__getitem__)
29
+ description, remedy = {
30
+ COMPARISON[0]: ("their configurations lack enough shared task-mix cells (overlap)",
31
+ "read more comparable history with shared task-mix cells (--all or a larger --since)"),
32
+ COMPARISON[1]: ("their task-mix cells are not shared with another configuration",
33
+ "read more comparable history with shared task-mix cells (--all or a larger --since)"),
34
+ COMPARISON[2]: (f"their configurations fall below the minimum run count (--min-n {surface.get('min_n', 5)})",
35
+ "read more history (--all or a larger --since), or lower the minimum (--min-n 3)"),
36
+ EARLY[0]: ("they have unknown acceptance outcomes", "see which evidence counted as acceptance (--grading)"),
37
+ EARLY[1]: ("they have no usable cost", "check missing prices or token counts for the selected cost basis"),
38
+ EARLY[2]: ("they have no model label", "check that the imported history records model names"),
39
+ }[reason]
40
+ lines = [f"no workflow configuration is eligible; the largest exclusion is {counts[reason]:,} of {population}: {description}"]
41
+ early = [f"{counts[r]:,} {r}" for r in EARLY if counts[r]]
42
+ if reason in COMPARISON and early:
43
+ lines.append("before comparison: " + ", ".join(early))
44
+ lines.append("to get a table: " + remedy)
45
+ return lines
@@ -14,6 +14,8 @@ from pathlib import Path
14
14
  from typing import Any, Iterable
15
15
 
16
16
  from .terminal import PRIVACY_LINE, beat3_exclusions
17
+ from .eligibility import unavailable_lines
18
+ from ..surface_presentation import shown_band_support_note
17
19
 
18
20
 
19
21
  _STYLE = """
@@ -130,7 +132,7 @@ def _configuration_table(surface: dict[str, Any]) -> str:
130
132
  key=_sort_value,
131
133
  )
132
134
  if not rows:
133
- return '<p class="note">No configurations met the minimum run count.</p>'
135
+ return "".join(f'<p class="note">{escape(line)}</p>' for line in unavailable_lines(surface))
134
136
 
135
137
  scale_low, scale_high = _band_positions(rows)
136
138
  span = scale_high - scale_low
@@ -237,8 +239,9 @@ def render(
237
239
  exclusions = "".join(f"<li>{escape(line.strip())}</li>" for line in exclusion_lines)
238
240
  notices = [
239
241
  surface.get(key)
240
- for key in ("tier_note", "overlap_caveat", "band_note", "band_support_note")
242
+ for key in ("tier_note", "overlap_caveat", "band_note")
241
243
  ]
244
+ notices.append(shown_band_support_note(surface))
242
245
  notices.extend(price_warnings or [])
243
246
  notes_html = "".join(f'<p class="note">{escape(str(note))}</p>' for note in notices if note)
244
247
  coverage_text = coverage_line or "No grading coverage available."
@@ -137,24 +137,9 @@ PRIVACY_LINE = "Local only. Nothing uploaded."
137
137
 
138
138
  def _no_configuration_lines(surface: dict) -> list[str]:
139
139
  """Why the table is empty, from the surface's own exclusion counts, and what to try."""
140
- min_n = surface.get("min_n")
141
- counts = {e.get("reason"): int(e.get("n") or 0) for e in surface.get("exclusions") or []}
142
- n_rows = surface.get("n_rows")
143
- head = (f" no workflow configuration has {min_n} runs with a known outcome (the minimum, --min-n)"
144
- if min_n else " no configurations met the minimum run count")
145
- lines = [head]
146
- parts = []
147
- for reason, words in (("unknown acceptance", "had no acceptance evidence in the logs"),
148
- ("configuration below min_n", "are in configurations with fewer runs than that"),
149
- ("no usable cost value", "had no usable cost"),
150
- ("no model label", "had no model name")):
151
- if counts.get(reason):
152
- parts.append(f"{_fmt_int(counts[reason])} {words}")
153
- if parts and n_rows:
154
- lines.append(f" of {_fmt_int(n_rows)} runs read, " + ", ".join(parts))
155
- lines.append(" to get a table: read more history (--all, or a larger --since), lower the minimum "
156
- "(--min-n 3), or see which evidence counted as acceptance (--grading)")
157
- return lines
140
+ from .eligibility import unavailable_lines
141
+
142
+ return [" " + line for line in unavailable_lines(surface)]
158
143
 
159
144
 
160
145
  def beat2_configurations(
@@ -113,7 +113,8 @@ def spawn_fit(home: Path, *, no_prior: bool | None = None, without: tuple[str, .
113
113
 
114
114
  With neither `no_prior` nor `without` given (the store's own refits) the fit leaves out what the
115
115
  latest fit left out, decided when it runs; `fit --background` passes its flags, even empty ones."""
116
- home = Path(home)
116
+ # Anchor paths in the caller before the child changes its working directory.
117
+ home = Path(home).expanduser().resolve()
117
118
  fits = _fits(home)
118
119
  inherit = no_prior is None and without is None
119
120
  opts = _opts(bool(no_prior), without or (), full, inherit=inherit)
@@ -132,6 +133,9 @@ def spawn_fit(home: Path, *, no_prior: bool | None = None, without: tuple[str, .
132
133
  if full:
133
134
  cmd.append("--full")
134
135
  env = dict(os.environ)
136
+ env["LOOPMATH_HOME"] = str(home)
137
+ if env.get("LOOPMATH_CACHE_DIR"):
138
+ env["LOOPMATH_CACHE_DIR"] = str(Path(env["LOOPMATH_CACHE_DIR"]).expanduser().resolve())
135
139
  root = _package_root()
136
140
  env["PYTHONPATH"] = root + (os.pathsep + env["PYTHONPATH"] if env.get("PYTHONPATH") else "")
137
141
  log_path = fits / "fit.log"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loopmath
3
- Version: 0.2.4
3
+ Version: 0.2.5
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
@@ -241,7 +241,7 @@ The store is `~/.loopmath`. Set `LOOPMATH_HOME` or pass `--home` to put it elsew
241
241
  - signals, receipts and fits;
242
242
  - your config and your workflows;
243
243
  - the views you write;
244
- - the caches: the log parse cache, and the folder where `fit --full` compiles its model (when that compile fails, it also leaves the C code it tried in the system temp folder and prints where).
244
+ - the caches: the log parse cache, and the folder where `fit --full` compiles its model (when that compile fails, it also leaves the C code it tried in a `pytensor_compilation_error_*` file in the system temp folder and prints where).
245
245
 
246
246
  Do not edit the store by hand; use the commands. `loopmath verify-receipts` checks the receipts in your store; give a file to verify a research receipt ledger instead.
247
247
 
@@ -260,7 +260,7 @@ loopmath analyze # cost per accepted run from the last 14 days o
260
260
  loopmath graph --workspace NAME --format html --out graph.html
261
261
  ```
262
262
 
263
- `analyze` reads `~/.claude/projects` and `~/.codex/sessions`. It grades the outcome evidence it finds, prices known models, and prints cost per accepted run with an 80% band. It also lists what it could not use and why; when no workflow configuration has enough runs to compare, it says how many runs it read and what kept each out, and what to try (`--min-n`, `--grading`). `loopmath analyze --json` prints the same as one JSON object, and `--home PATH` keeps its parse cache in that store.
263
+ `analyze` reads `~/.claude/projects` and `~/.codex/sessions`. It grades the outcome evidence it finds, prices known models, and prints cost per accepted run with an 80% band. It also lists what it could not use and why; when no workflow configuration is eligible to compare, it names the largest comparison exclusion, its count, and what to try. `loopmath analyze --json` prints the same as one JSON object, and `--home PATH` keeps its parse cache in that store.
264
264
 
265
265
  `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
266
 
@@ -180,6 +180,8 @@ src/loopmath/ocp/commands.py
180
180
  src/loopmath/ocp/conformance.py
181
181
  src/loopmath/ocp/contractv3.py
182
182
  src/loopmath/ocp/emit.py
183
+ src/loopmath/ocp/graph_contract.py
184
+ src/loopmath/ocp/membership.py
183
185
  src/loopmath/ocp/migrate.py
184
186
  src/loopmath/ocp/version.py
185
187
  src/loopmath/ocp/schema/ocp-v0.2.schema.json
@@ -223,6 +225,7 @@ src/loopmath/recommend/search.py
223
225
  src/loopmath/recommend/stats.py
224
226
  src/loopmath/recommend/storeread.py
225
227
  src/loopmath/report/__init__.py
228
+ src/loopmath/report/eligibility.py
226
229
  src/loopmath/report/html.py
227
230
  src/loopmath/report/terminal.py
228
231
  src/loopmath/report/terminal_exclusions.py
@@ -361,6 +364,7 @@ tests/test_preview_script.py
361
364
  tests/test_price.py
362
365
  tests/test_receipts.py
363
366
  tests/test_release_paths.py
367
+ tests/test_report_eligibility.py
364
368
  tests/test_research_cli.py
365
369
  tests/test_runfile_privacy_n1b.py
366
370
  tests/test_scoring.py