lean-runtime 2.6.0__tar.gz → 2.6.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 (171) hide show
  1. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/CHANGELOG.md +13 -0
  2. {lean_runtime-2.6.0/lean_runtime.egg-info → lean_runtime-2.6.1}/PKG-INFO +7 -1
  3. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/README.md +6 -0
  4. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/cli.md +8 -0
  5. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/getting-started.md +17 -0
  6. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/local-projects.md +10 -0
  7. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/cli.py +13 -3
  8. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/project_sharing.py +9 -0
  9. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/runtime.py +114 -13
  10. {lean_runtime-2.6.0 → lean_runtime-2.6.1/lean_runtime.egg-info}/PKG-INFO +7 -1
  11. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/pyproject.toml +1 -1
  12. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_cli.py +1 -0
  13. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_programs.py +1 -1
  14. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_projects.py +131 -6
  15. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/LICENSE +0 -0
  16. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/MANIFEST.in +0 -0
  17. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/catalog/environments.toml +0 -0
  18. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/catalog/locks/core-v4.32.2.lock.json +0 -0
  19. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/catalog/locks/leancert-v4.30.0.5.lock.json +0 -0
  20. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/catalog/locks/leancert-v4.31.0.lock.json +0 -0
  21. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/catalog/locks/leancert-v4.32.2.4.lock.json +0 -0
  22. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/catalog/locks/leancert-v4.33.0.lock.json +0 -0
  23. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/catalog/locks/mathlib-v4.30.0.lock.json +0 -0
  24. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/catalog/locks/mathlib-v4.31.0.lock.json +0 -0
  25. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/catalog/locks/mathlib-v4.32.2.lock.json +0 -0
  26. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/catalog/locks/mathlib-v4.33.0.lock.json +0 -0
  27. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/compatibility/README.md +0 -0
  28. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/compatibility/mathlib-4.32.2.json +0 -0
  29. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/compatibility/mathlib-4.32.2.toml +0 -0
  30. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/compatibility/mathlib-4.33.0.json +0 -0
  31. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/compatibility/mathlib-4.33.0.toml +0 -0
  32. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/architecture.md +0 -0
  33. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/captures.md +0 -0
  34. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/case-study-v1.md +0 -0
  35. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/development.md +0 -0
  36. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/environments.md +0 -0
  37. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/index.md +0 -0
  38. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/portable-copies.md +0 -0
  39. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/project-publishing.md +0 -0
  40. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/python-api.md +0 -0
  41. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/ready-programs.md +0 -0
  42. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/standalone-files.md +0 -0
  43. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/trust-and-limitations.md +0 -0
  44. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/docs/v1-precision.md +0 -0
  45. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/examples/mathlib.toml +0 -0
  46. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/__init__.py +0 -0
  47. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/__main__.py +0 -0
  48. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/_git.py +0 -0
  49. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/_paths.py +0 -0
  50. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/backends.py +0 -0
  51. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/bundles.py +0 -0
  52. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/capsules.py +0 -0
  53. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/comparison.py +0 -0
  54. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/console.py +0 -0
  55. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/decisions.py +0 -0
  56. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/diagnostics.py +0 -0
  57. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/__init__.py +0 -0
  58. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/analyzer.py +0 -0
  59. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/api.py +0 -0
  60. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/candidate.py +0 -0
  61. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/catalog.py +0 -0
  62. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/catalog_build.py +0 -0
  63. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/catalog_cli.py +0 -0
  64. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/catalog_manifest.py +0 -0
  65. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/data/catalog.json +0 -0
  66. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/defaults.py +0 -0
  67. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/engine.py +0 -0
  68. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/errors.py +0 -0
  69. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/module_inventory.py +0 -0
  70. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/planner.py +0 -0
  71. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/policy.py +0 -0
  72. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/probe.py +0 -0
  73. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/py.typed +0 -0
  74. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/result.py +0 -0
  75. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/schema_resources.py +0 -0
  76. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/schemas/catalog-v1.schema.json +0 -0
  77. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/schemas/plan-v1.schema.json +0 -0
  78. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/discovery/schemas/result-v1.schema.json +0 -0
  79. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/environments.py +0 -0
  80. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/errors.py +0 -0
  81. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/events.py +0 -0
  82. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/facade.py +0 -0
  83. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/frontmatter.py +0 -0
  84. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/health.py +0 -0
  85. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/lake.py +0 -0
  86. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/lockfiles.py +0 -0
  87. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/locking.py +0 -0
  88. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/matrix.py +0 -0
  89. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/models.py +0 -0
  90. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/oci.py +0 -0
  91. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/packs.py +0 -0
  92. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/policies.py +0 -0
  93. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/profiling.py +0 -0
  94. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/programs.py +0 -0
  95. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/projects.py +0 -0
  96. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/publisher_verification.py +0 -0
  97. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/py.typed +0 -0
  98. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/references.py +0 -0
  99. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/resolver.py +0 -0
  100. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/run_cli.py +0 -0
  101. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/schema_resources.py +0 -0
  102. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/serialization.py +0 -0
  103. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/shared_projects.py +0 -0
  104. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/specs.py +0 -0
  105. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/store.py +0 -0
  106. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/timings.py +0 -0
  107. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/toolchain_oci.py +0 -0
  108. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/toolchain_slim.py +0 -0
  109. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/toolchains.py +0 -0
  110. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/verification.py +0 -0
  111. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime/wire.py +0 -0
  112. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime.egg-info/SOURCES.txt +0 -0
  113. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime.egg-info/dependency_links.txt +0 -0
  114. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime.egg-info/entry_points.txt +0 -0
  115. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime.egg-info/requires.txt +0 -0
  116. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/lean_runtime.egg-info/top_level.txt +0 -0
  117. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/mkdocs.yml +0 -0
  118. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/schemas/cleanup-v1.schema.json +0 -0
  119. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/schemas/comparison-v1.schema.json +0 -0
  120. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/schemas/execution-v1.schema.json +0 -0
  121. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/schemas/inspect-v1.schema.json +0 -0
  122. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/schemas/matrix-v1.schema.json +0 -0
  123. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/schemas/profile-v1.schema.json +0 -0
  124. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/schemas/verify-v1.schema.json +0 -0
  125. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/scripts/announcement_gate.py +0 -0
  126. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/scripts/registry_preflight.py +0 -0
  127. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/scripts/run_compatibility.py +0 -0
  128. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/scripts/run_v1_case_study.py +0 -0
  129. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/scripts/smoke_wheel.py +0 -0
  130. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/setup.cfg +0 -0
  131. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/conftest.py +0 -0
  132. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/discovery/e2e/test_real_discovery.py +0 -0
  133. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/discovery/test_analyzer.py +0 -0
  134. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/discovery/test_catalog.py +0 -0
  135. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/discovery/test_catalog_build.py +0 -0
  136. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/discovery/test_discovery_schema_resources.py +0 -0
  137. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/discovery/test_engine.py +0 -0
  138. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/discovery/test_planner.py +0 -0
  139. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/discovery/test_policy.py +0 -0
  140. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_backend.py +0 -0
  141. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_bundles.py +0 -0
  142. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_capsules.py +0 -0
  143. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_console.py +0 -0
  144. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_diagnostics.py +0 -0
  145. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_environment_integration.py +0 -0
  146. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_events_health.py +0 -0
  147. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_facade.py +0 -0
  148. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_frontmatter.py +0 -0
  149. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_git.py +0 -0
  150. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_interactive.py +0 -0
  151. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_lockfiles.py +0 -0
  152. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_models.py +0 -0
  153. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_multifile.py +0 -0
  154. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_packs.py +0 -0
  155. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_paths.py +0 -0
  156. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_prebuilt_policy.py +0 -0
  157. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_project_publish.py +0 -0
  158. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_references.py +0 -0
  159. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_run_cli.py +0 -0
  160. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_runtime.py +0 -0
  161. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_schema_resources.py +0 -0
  162. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_schemas.py +0 -0
  163. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_signatures.py +0 -0
  164. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_specs.py +0 -0
  165. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_store.py +0 -0
  166. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_toolchain_oci.py +0 -0
  167. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_toolchain_slim.py +0 -0
  168. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_toolchains.py +0 -0
  169. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_v1_precision.py +0 -0
  170. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_verification_inventory.py +0 -0
  171. {lean_runtime-2.6.0 → lean_runtime-2.6.1}/tests/test_verification_probe.py +0 -0
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 2.6.1 - 2026-08-14
6
+
7
+ - Allow `lean-runtime init .` at an otherwise empty Git repository root while
8
+ preserving its original `.git` directory or worktree file, HEAD, and index.
9
+ Planning and execution now share target validation, so `init --plan` rejects
10
+ unsupported existing contents before reporting an acquisition-ready plan.
11
+ - Add `init --name NAME` for repository directories whose filesystem spelling
12
+ should not determine the Lake package and Lean root module name. Repeating
13
+ the same named initialization is idempotent; a conflicting name is rejected.
14
+ - Make `init --plan --offline` and `update --plan --offline` genuinely
15
+ network-free. Plans now report explicit blockers and return a failing status
16
+ when an exact local graph or required full toolchain is unavailable.
17
+
5
18
  ## 2.6.0 - 2026-08-14
6
19
 
7
20
  - Make the local-project path a four-command workflow: `init`, `check`,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-runtime
3
- Version: 2.6.0
3
+ Version: 2.6.1
4
4
  Summary: Run Lean 4 proofs from Python or standalone files
5
5
  Author: Alejandro Radisic
6
6
  License-Expression: Apache-2.0
@@ -209,6 +209,12 @@ lean-runtime init .
209
209
  `init --plan` is side-effect free; `--max-download 500MiB` and `--offline`
210
210
  enforce cold-start policy. Advanced bulk migration remains available through
211
211
  `attach`, and `detach --execute` materializes an independent project again.
212
+ For a new project, the target may be absent, empty, or an otherwise empty Git
213
+ repository root; existing Git identity and index state are preserved. A custom
214
+ `AGENTS.md` is also allowed and retained. Other existing contents are rejected
215
+ before acquisition rather than overwritten.
216
+ When the directory spelling is not the intended Lean module capitalization,
217
+ set it explicitly, for example `lean-runtime init . --name IntegralFramework`.
212
218
 
213
219
  One-shot helpers are available when setup reuse is unnecessary:
214
220
 
@@ -173,6 +173,12 @@ lean-runtime init .
173
173
  `init --plan` is side-effect free; `--max-download 500MiB` and `--offline`
174
174
  enforce cold-start policy. Advanced bulk migration remains available through
175
175
  `attach`, and `detach --execute` materializes an independent project again.
176
+ For a new project, the target may be absent, empty, or an otherwise empty Git
177
+ repository root; existing Git identity and index state are preserved. A custom
178
+ `AGENTS.md` is also allowed and retained. Other existing contents are rejected
179
+ before acquisition rather than overwritten.
180
+ When the directory spelling is not the intended Lean module capitalization,
181
+ set it explicitly, for example `lean-runtime init . --name IntegralFramework`.
176
182
 
177
183
  One-shot helpers are available when setup reuse is unnecessary:
178
184
 
@@ -177,6 +177,14 @@ recovery, and adopts each valid project transactionally. `detach` is likewise a
177
177
  preview unless `--execute` is present. Attached projects use shared mode by
178
178
  default in `lean-runtime build`; detach before requesting `--local`.
179
179
 
180
+ A new `init` target may be absent, empty, or an otherwise empty Git root, with
181
+ an optional existing `AGENTS.md`. Git metadata and the custom guide are
182
+ preserved. Planning rejects any other existing contents using the same
183
+ validation as execution.
184
+ `--name NAME` overrides the inferred Lake package/root module name for a new
185
+ project, which is useful when a lowercase repository name needs internal
186
+ capitalization such as `IntegralFramework`.
187
+
180
188
  Add supporting source files with repeatable `--include` options:
181
189
 
182
190
  ```bash
@@ -37,10 +37,27 @@ Before doing any work, `init --plan` reports the exact release, local reuse, and
37
37
  known download size. `--max-download 500MiB` refuses a larger transfer and
38
38
  `--offline` requires a matching local graph. Initialization is transactional:
39
39
  the target appears only after Lake and the shared dependency graph both verify.
40
+ Offline plans perform no registry lookup; they return a blocked plan and a
41
+ nonzero status when the required exact local graph is absent.
40
42
  Project development requires one full Lake-capable Lean toolchain per Lean
41
43
  version. When it is absent, the plan says its Elan download size is unknown;
42
44
  `--offline` and `--max-download` fail closed instead of silently installing it.
43
45
 
46
+ The target may be a missing directory, an empty directory, or an otherwise
47
+ empty Git repository root. In the Git case, `init` preserves the existing HEAD,
48
+ index, remotes, and worktree metadata. It also preserves an existing
49
+ `AGENTS.md`; any other non-project contents are rejected by both `--plan` and
50
+ execution rather than overwritten.
51
+ The directory name normally supplies the Lake package/root module name. Override
52
+ it when capitalization matters, for example:
53
+
54
+ ```bash
55
+ lean-runtime init . --name IntegralFramework
56
+ ```
57
+
58
+ The same command can be rerun safely after initialization; a different name is
59
+ rejected rather than silently renaming an existing project.
60
+
44
61
  Dependency upgrades are explicit:
45
62
 
46
63
  ```bash
@@ -61,11 +61,21 @@ Initialization acquires and verifies the exact graph before publishing the
61
61
  target directory. Use `--plan` for a side-effect-free cost report, `--offline`
62
62
  to require local data, `--max-download SIZE` to enforce a transfer ceiling, or
63
63
  `--seed-from PROJECT` to name an exact local donor.
64
+ Offline planning never queries configured registries. A missing exact local
65
+ graph appears as a blocker and makes the planning command fail.
64
66
  The plan separately reports whether the full Lake-capable toolchain is already
65
67
  installed. Because Elan does not publish a preflight byte count, a missing full
66
68
  toolchain is rejected under `--offline` or `--max-download` rather than silently
67
69
  bypassing the policy.
68
70
 
71
+ An otherwise empty Git root is a valid new-project target. The original `.git`
72
+ directory—or `.git` worktree file—is transferred through the atomic publication
73
+ without changing HEAD, the index, or remotes. A pre-existing `AGENTS.md` is also
74
+ preserved. Other contents must be moved first or represented by an existing
75
+ pinned Lake project; the read-only plan enforces the same rule as execution.
76
+ Use `--name NAME` when creating inside a lowercase or otherwise differently
77
+ named repository and the Lean root module needs an explicit spelling.
78
+
69
79
  For an existing pinned project, `lean-runtime init .` adopts its current
70
80
  manifest without changing versions. Register a collection once with
71
81
  `lean-runtime scan ~/research`; future exact matches are preferred over
@@ -12,7 +12,7 @@ from typing import Any
12
12
 
13
13
  from .console import styler_for
14
14
  from .environments import ExecutionCapture
15
- from .errors import LeanRuntimeError, MaterializationError, ResolutionError
15
+ from .errors import LeanRuntimeError, MaterializationError, ProjectError, ResolutionError
16
16
  from .events import RuntimeEvent
17
17
  from .lake import ROOT_MODULE
18
18
  from .lockfiles import EnvironmentLock
@@ -184,7 +184,8 @@ def _render_init_plan(plan: ProjectInitPlan) -> None:
184
184
  print(f"Exact dependencies: {len(plan.packages)} package(s); versions unchanged")
185
185
  return
186
186
  context = f"Mathlib {plan.mathlib_version}" if plan.mathlib_version else "core Lean"
187
- print(f"Create {plan.root.name} · {context} · {plan.toolchain}")
187
+ name = plan.project_name or plan.root.name
188
+ print(f"Create {name} in {plan.root} · {context} · {plan.toolchain}")
188
189
  if not plan.toolchain_installed:
189
190
  print("Full Lake toolchain: download required (size not published by Elan)")
190
191
  if plan.seed_root is not None:
@@ -194,6 +195,8 @@ def _render_init_plan(plan: ProjectInitPlan) -> None:
194
195
  print(f"Download: {format_byte_size(plan.download_bytes)}")
195
196
  else:
196
197
  print("Download: unknown (no compatible published artifact could be priced)")
198
+ for blocker in plan.blockers:
199
+ print(f"Blocker: {blocker}")
197
200
 
198
201
 
199
202
  def _render_update_plan(plan: ProjectUpdatePlan) -> None:
@@ -538,6 +541,7 @@ def parser() -> argparse.ArgumentParser:
538
541
  "init", help="create a latest-Mathlib project or adopt an existing Lake project"
539
542
  )
540
543
  init.add_argument("path", type=Path, nargs="?", default=Path("."))
544
+ init.add_argument("--name", help="explicit Lake package and root module name")
541
545
  init_context = init.add_mutually_exclusive_group()
542
546
  init_context.add_argument(
543
547
  "--mathlib",
@@ -666,6 +670,7 @@ def main(argv: list[str] | None = None) -> int:
666
670
  if args.command == "init":
667
671
  init_plan = runtime.plan_project_init(
668
672
  args.path,
673
+ name=args.name,
669
674
  mathlib=args.mathlib,
670
675
  toolchain=args.toolchain,
671
676
  seed_from=args.seed_from,
@@ -675,11 +680,16 @@ def main(argv: list[str] | None = None) -> int:
675
680
  _json(init_plan.to_dict())
676
681
  else:
677
682
  _render_init_plan(init_plan)
678
- return 0
683
+ return 0 if init_plan.ready else 1
684
+ if not init_plan.ready:
685
+ raise ProjectError(
686
+ "project cannot be initialized:\n- " + "\n- ".join(init_plan.blockers)
687
+ )
679
688
  if not args.json:
680
689
  _render_init_plan(init_plan)
681
690
  init_result = runtime.init_project(
682
691
  args.path,
692
+ name=args.name,
683
693
  mathlib=args.mathlib,
684
694
  toolchain=args.toolchain,
685
695
  agents=args.agents,
@@ -232,6 +232,12 @@ class ProjectInitPlan:
232
232
  download_bytes_complete: bool
233
233
  already_attached: bool = False
234
234
  toolchain_installed: bool = False
235
+ project_name: str | None = None
236
+ blockers: tuple[str, ...] = ()
237
+
238
+ @property
239
+ def ready(self) -> bool:
240
+ return not self.blockers
235
241
 
236
242
  def to_dict(self) -> dict[str, Any]:
237
243
  return {
@@ -245,6 +251,9 @@ class ProjectInitPlan:
245
251
  "download_bytes_complete": self.download_bytes_complete,
246
252
  "already_attached": self.already_attached,
247
253
  "toolchain_installed": self.toolchain_installed,
254
+ "project_name": self.project_name,
255
+ "ready": self.ready,
256
+ "blockers": list(self.blockers),
248
257
  }
249
258
 
250
259
 
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import json
6
6
  import os
7
7
  import subprocess
8
+ import sys
8
9
  import tempfile
9
10
  import threading
10
11
  from collections.abc import Mapping, Sequence
@@ -14,6 +15,11 @@ from datetime import datetime, timezone
14
15
  from pathlib import Path
15
16
  from typing import Any
16
17
 
18
+ if sys.version_info >= (3, 11):
19
+ import tomllib
20
+ else: # pragma: no cover - exercised by the Python 3.10 CI job
21
+ import tomli as tomllib
22
+
17
23
  from ._paths import remove_tree
18
24
  from .backends import Backend, LocalBackend
19
25
  from .bundles import EnvironmentBundles, PortableCopyInfo
@@ -1302,6 +1308,18 @@ class Runtime:
1302
1308
  )
1303
1309
  self.toolchains.ensure(toolchain)
1304
1310
 
1311
+ def _project_toolchain_blockers(self, toolchain: str, *, installed: bool) -> tuple[str, ...]:
1312
+ if installed:
1313
+ return ()
1314
+ if self.availability == "local":
1315
+ return (f"offline setup requires the full {toolchain} toolchain locally",)
1316
+ if self.max_download_bytes is not None:
1317
+ return (
1318
+ "the full Lake-capable Lean toolchain is missing and its Elan download "
1319
+ "cannot be preflighted under --max-download",
1320
+ )
1321
+ return ()
1322
+
1305
1323
  def plan_project_update(
1306
1324
  self,
1307
1325
  path: str | os.PathLike[str] = ".",
@@ -1370,16 +1388,24 @@ class Runtime:
1370
1388
  download_bytes: int | None = 0
1371
1389
  complete = True
1372
1390
  if changed:
1391
+ blockers.extend(
1392
+ self._project_toolchain_blockers(latest.toolchain, installed=toolchain_installed)
1393
+ )
1373
1394
  seeds, seed_root = self._project_seed_paths(
1374
1395
  latest.toolchain,
1375
1396
  latest_packages,
1376
1397
  seed_from=Path(seed_from) if seed_from is not None else None,
1377
1398
  )
1378
1399
  if not seeds:
1379
- acquisition = self.plan_exact(latest.lock, import_roots=("Mathlib",))
1380
- raw_download = acquisition.get("download_bytes")
1381
- download_bytes = raw_download if isinstance(raw_download, int) else None
1382
- complete = acquisition.get("download_bytes_complete") is True
1400
+ if self.availability == "local":
1401
+ download_bytes = None
1402
+ complete = False
1403
+ blockers.append("offline update has no exact local Mathlib dependency graph")
1404
+ else:
1405
+ acquisition = self.plan_exact(latest.lock, import_roots=("Mathlib",))
1406
+ raw_download = acquisition.get("download_bytes")
1407
+ download_bytes = raw_download if isinstance(raw_download, int) else None
1408
+ complete = acquisition.get("download_bytes_complete") is True
1383
1409
  if not toolchain_installed:
1384
1410
  download_bytes = None
1385
1411
  complete = False
@@ -1564,10 +1590,29 @@ class Runtime:
1564
1590
  return registered, root
1565
1591
  return {}, None
1566
1592
 
1593
+ @staticmethod
1594
+ def _validate_new_project_target(target: Path) -> tuple[Path, ...]:
1595
+ """Validate metadata that can be preserved around an atomic project init."""
1596
+ if not target.exists():
1597
+ return ()
1598
+ if not target.is_dir():
1599
+ raise ProjectError(f"initialization target is not a directory: {target}")
1600
+ entries = tuple(target.iterdir())
1601
+ allowed = {".git", "AGENTS.md"}
1602
+ unsupported = [entry for entry in entries if entry.name not in allowed]
1603
+ if unsupported:
1604
+ names = ", ".join(sorted(entry.name for entry in unsupported))
1605
+ raise ProjectError(
1606
+ f"initialization target is not empty: {target} ({names}); choose an empty "
1607
+ "directory or run `lean-runtime init .` inside an existing Lake project"
1608
+ )
1609
+ return entries
1610
+
1567
1611
  def plan_project_init(
1568
1612
  self,
1569
1613
  path: str | os.PathLike[str] = ".",
1570
1614
  *,
1615
+ name: str | None = None,
1571
1616
  mathlib: str | None = "latest",
1572
1617
  toolchain: str | None = None,
1573
1618
  seed_from: str | os.PathLike[str] | None = None,
@@ -1575,12 +1620,30 @@ class Runtime:
1575
1620
  """Plan project creation or adoption without changing the target."""
1576
1621
  target = Path(path).expanduser().resolve()
1577
1622
  if (target / "lakefile.toml").is_file() or (target / "lakefile.lean").is_file():
1623
+ if name is not None:
1624
+ lakefile = target / "lakefile.toml"
1625
+ if not lakefile.is_file():
1626
+ raise ProjectError(
1627
+ "cannot verify --name for an existing lakefile.lean project; omit it"
1628
+ )
1629
+ try:
1630
+ with lakefile.open("rb") as stream:
1631
+ configured_name = tomllib.load(stream).get("name")
1632
+ except (OSError, tomllib.TOMLDecodeError) as exc:
1633
+ raise ProjectError(f"could not verify existing project --name: {exc}") from exc
1634
+ if configured_name != name:
1635
+ raise ProjectError(
1636
+ f"existing project is named {configured_name!r}, not {name!r}"
1637
+ )
1578
1638
  adoption = self.plan_project_adoption(target).projects[0]
1579
1639
  installed = (
1580
1640
  self._toolchain_installed(adoption.toolchain)
1581
1641
  if adoption.toolchain is not None
1582
1642
  else False
1583
1643
  )
1644
+ blockers = tuple(adoption.blockers) + self._project_toolchain_blockers(
1645
+ adoption.toolchain or "unknown", installed=installed
1646
+ )
1584
1647
  return ProjectInitPlan(
1585
1648
  target,
1586
1649
  "adopt",
@@ -1592,7 +1655,17 @@ class Runtime:
1592
1655
  True,
1593
1656
  adoption.attached,
1594
1657
  installed,
1658
+ None,
1659
+ blockers,
1595
1660
  )
1661
+ self._validate_new_project_target(target)
1662
+ project_name = name or target.name
1663
+ if name is not None and (
1664
+ not name
1665
+ or not (name[0].isalpha() or name[0] == "_")
1666
+ or any(not (character.isalnum() or character == "_") for character in name)
1667
+ ):
1668
+ raise ProjectError("project --name must be a Lean identifier")
1596
1669
  selected = self._mathlib_catalog_entry(mathlib or "latest")
1597
1670
  selected_toolchain = normalize_toolchain(toolchain or selected.toolchain)
1598
1671
  if mathlib is not None and selected_toolchain != selected.toolchain:
@@ -1602,6 +1675,7 @@ class Runtime:
1602
1675
  )
1603
1676
  if mathlib is None:
1604
1677
  installed = self._toolchain_installed(selected_toolchain)
1678
+ blockers = self._project_toolchain_blockers(selected_toolchain, installed=installed)
1605
1679
  return ProjectInitPlan(
1606
1680
  target,
1607
1681
  "create",
@@ -1613,6 +1687,8 @@ class Runtime:
1613
1687
  installed,
1614
1688
  False,
1615
1689
  installed,
1690
+ project_name,
1691
+ blockers,
1616
1692
  )
1617
1693
  selected_packages = selected.lock.manifest["packages"]
1618
1694
  assert isinstance(selected_packages, list)
@@ -1621,15 +1697,25 @@ class Runtime:
1621
1697
  selected_packages,
1622
1698
  seed_from=Path(seed_from) if seed_from is not None else None,
1623
1699
  )
1700
+ toolchain_installed = self._toolchain_installed(selected_toolchain)
1701
+ init_blockers = list(
1702
+ self._project_toolchain_blockers(
1703
+ selected_toolchain,
1704
+ installed=toolchain_installed,
1705
+ )
1706
+ )
1624
1707
  if seed_paths:
1625
1708
  download_bytes: int | None = 0
1626
1709
  complete = True
1710
+ elif self.availability == "local":
1711
+ download_bytes = None
1712
+ complete = False
1713
+ init_blockers.append("offline setup has no exact local Mathlib dependency graph")
1627
1714
  else:
1628
1715
  acquisition = self.plan_exact(selected.lock, import_roots=("Mathlib",))
1629
1716
  raw_download = acquisition.get("download_bytes")
1630
1717
  download_bytes = raw_download if isinstance(raw_download, int) else None
1631
1718
  complete = acquisition.get("download_bytes_complete") is True
1632
- toolchain_installed = self._toolchain_installed(selected_toolchain)
1633
1719
  if not toolchain_installed:
1634
1720
  download_bytes = None
1635
1721
  complete = False
@@ -1644,12 +1730,15 @@ class Runtime:
1644
1730
  complete,
1645
1731
  False,
1646
1732
  toolchain_installed,
1733
+ project_name,
1734
+ tuple(init_blockers),
1647
1735
  )
1648
1736
 
1649
1737
  def init_project(
1650
1738
  self,
1651
1739
  path: str | os.PathLike[str] = ".",
1652
1740
  *,
1741
+ name: str | None = None,
1653
1742
  mathlib: str | None = "latest",
1654
1743
  toolchain: str | None = None,
1655
1744
  agents: bool = True,
@@ -1670,6 +1759,7 @@ class Runtime:
1670
1759
 
1671
1760
  plan = self.plan_project_init(
1672
1761
  target,
1762
+ name=name,
1673
1763
  mathlib=mathlib,
1674
1764
  toolchain=toolchain,
1675
1765
  seed_from=seed_from,
@@ -1697,18 +1787,14 @@ class Runtime:
1697
1787
  seed_packages = environment.workspace / str(raw_packages)
1698
1788
  seed_package_paths = None
1699
1789
  target.parent.mkdir(parents=True, exist_ok=True)
1700
- existing_entries = tuple(target.iterdir()) if target.is_dir() else ()
1701
- unsupported = [entry for entry in existing_entries if entry.name != "AGENTS.md"]
1702
- if unsupported:
1703
- raise ProjectError(
1704
- f"initialization target is not empty: {target}; use `lean-runtime init .` "
1705
- "inside an existing Lake project to adopt it"
1706
- )
1790
+ existing_entries = self._validate_new_project_target(target)
1707
1791
  staging = Path(
1708
1792
  tempfile.mkdtemp(prefix=f".{target.name}.lean-runtime-init-", dir=target.parent)
1709
1793
  )
1710
1794
  backup: Path | None = None
1711
1795
  published = False
1796
+ original_git = next((entry for entry in existing_entries if entry.name == ".git"), None)
1797
+ git_transferred = False
1712
1798
  try:
1713
1799
  custom_agents = target / "AGENTS.md"
1714
1800
  if custom_agents.is_file():
@@ -1719,7 +1805,7 @@ class Runtime:
1719
1805
  "--offline",
1720
1806
  f"--dir={staging}",
1721
1807
  "init",
1722
- target.name,
1808
+ plan.project_name or target.name,
1723
1809
  "lib",
1724
1810
  )
1725
1811
  process = subprocess.run(
@@ -1798,6 +1884,14 @@ class Runtime:
1798
1884
  agents_file = staging / "AGENTS.md"
1799
1885
  if agents and not agents_file.exists():
1800
1886
  agents_file.write_text(_DEFAULT_AGENTS_GUIDE, encoding="utf-8")
1887
+ if original_git is not None:
1888
+ generated_git = staging / ".git"
1889
+ if generated_git.is_dir() and not generated_git.is_symlink():
1890
+ remove_tree(generated_git)
1891
+ elif generated_git.exists() or generated_git.is_symlink():
1892
+ generated_git.unlink()
1893
+ original_git.replace(generated_git)
1894
+ git_transferred = True
1801
1895
  if target.exists():
1802
1896
  backup = target.parent / f".{target.name}.lean-runtime-backup-{os.getpid()}"
1803
1897
  target.replace(backup)
@@ -1812,6 +1906,13 @@ class Runtime:
1812
1906
  self.shared_projects.remember_project(context)
1813
1907
  return replace(result.results[0], root=target)
1814
1908
  except BaseException:
1909
+ if git_transferred:
1910
+ staged_git = (target if published else staging) / ".git"
1911
+ restore_root = backup if backup is not None and backup.exists() else target
1912
+ if staged_git.exists() or staged_git.is_symlink():
1913
+ restore_root.mkdir(parents=True, exist_ok=True)
1914
+ staged_git.replace(restore_root / ".git")
1915
+ git_transferred = False
1815
1916
  if published:
1816
1917
  if target.exists():
1817
1918
  remove_tree(target)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-runtime
3
- Version: 2.6.0
3
+ Version: 2.6.1
4
4
  Summary: Run Lean 4 proofs from Python or standalone files
5
5
  Author: Alejandro Radisic
6
6
  License-Expression: Apache-2.0
@@ -209,6 +209,12 @@ lean-runtime init .
209
209
  `init --plan` is side-effect free; `--max-download 500MiB` and `--offline`
210
210
  enforce cold-start policy. Advanced bulk migration remains available through
211
211
  `attach`, and `detach --execute` materializes an independent project again.
212
+ For a new project, the target may be absent, empty, or an otherwise empty Git
213
+ repository root; existing Git identity and index state are preserved. A custom
214
+ `AGENTS.md` is also allowed and retained. Other existing contents are rejected
215
+ before acquisition rather than overwritten.
216
+ When the directory spelling is not the intended Lean module capitalization,
217
+ set it explicitly, for example `lean-runtime init . --name IntegralFramework`.
212
218
 
213
219
  One-shot helpers are available when setup reuse is unnecessary:
214
220
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lean-runtime"
7
- version = "2.6.0"
7
+ version = "2.6.1"
8
8
  description = "Run Lean 4 proofs from Python or standalone files"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -44,6 +44,7 @@ def test_project_sharing_commands_have_safe_defaults() -> None:
44
44
  assert init.mathlib == "4.33.0"
45
45
  assert init.agents
46
46
  assert parser().parse_args(["init", "demo", "--core"]).mathlib is None
47
+ assert parser().parse_args(["init", ".", "--name", "DemoProject"]).name == "DemoProject"
47
48
  assert not parser().parse_args(["init", "demo", "--no-agents"]).agents
48
49
  attach = parser().parse_args(["attach", "projects", "--recursive"])
49
50
  assert attach.recursive and not attach.execute
@@ -18,7 +18,7 @@ def _payload(root: Path) -> Path:
18
18
  payload = root / "payload"
19
19
  payload.mkdir()
20
20
  executable = payload / "echo-bridge"
21
- executable.write_text("#!/bin/sh\nwhile IFS= read -r line; do printf '%s\\n' \"$line\"; done\n")
21
+ executable.write_text("#!/bin/sh\nexec /bin/cat\n")
22
22
  executable.chmod(0o755)
23
23
  return payload
24
24