lean-runtime 2.6.0__tar.gz → 2.6.2__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.2}/CHANGELOG.md +26 -0
  2. {lean_runtime-2.6.0/lean_runtime.egg-info → lean_runtime-2.6.2}/PKG-INFO +9 -1
  3. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/README.md +8 -0
  4. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/cli.md +15 -0
  5. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/getting-started.md +19 -0
  6. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/local-projects.md +30 -8
  7. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/cli.py +30 -7
  8. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/project_sharing.py +77 -5
  9. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/runtime.py +149 -34
  10. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/shared_projects.py +123 -15
  11. {lean_runtime-2.6.0 → lean_runtime-2.6.2/lean_runtime.egg-info}/PKG-INFO +9 -1
  12. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/pyproject.toml +1 -1
  13. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_cli.py +8 -0
  14. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_programs.py +1 -1
  15. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_projects.py +189 -6
  16. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/LICENSE +0 -0
  17. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/MANIFEST.in +0 -0
  18. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/catalog/environments.toml +0 -0
  19. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/catalog/locks/core-v4.32.2.lock.json +0 -0
  20. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/catalog/locks/leancert-v4.30.0.5.lock.json +0 -0
  21. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/catalog/locks/leancert-v4.31.0.lock.json +0 -0
  22. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/catalog/locks/leancert-v4.32.2.4.lock.json +0 -0
  23. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/catalog/locks/leancert-v4.33.0.lock.json +0 -0
  24. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/catalog/locks/mathlib-v4.30.0.lock.json +0 -0
  25. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/catalog/locks/mathlib-v4.31.0.lock.json +0 -0
  26. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/catalog/locks/mathlib-v4.32.2.lock.json +0 -0
  27. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/catalog/locks/mathlib-v4.33.0.lock.json +0 -0
  28. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/compatibility/README.md +0 -0
  29. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/compatibility/mathlib-4.32.2.json +0 -0
  30. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/compatibility/mathlib-4.32.2.toml +0 -0
  31. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/compatibility/mathlib-4.33.0.json +0 -0
  32. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/compatibility/mathlib-4.33.0.toml +0 -0
  33. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/architecture.md +0 -0
  34. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/captures.md +0 -0
  35. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/case-study-v1.md +0 -0
  36. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/development.md +0 -0
  37. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/environments.md +0 -0
  38. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/index.md +0 -0
  39. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/portable-copies.md +0 -0
  40. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/project-publishing.md +0 -0
  41. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/python-api.md +0 -0
  42. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/ready-programs.md +0 -0
  43. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/standalone-files.md +0 -0
  44. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/trust-and-limitations.md +0 -0
  45. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/docs/v1-precision.md +0 -0
  46. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/examples/mathlib.toml +0 -0
  47. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/__init__.py +0 -0
  48. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/__main__.py +0 -0
  49. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/_git.py +0 -0
  50. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/_paths.py +0 -0
  51. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/backends.py +0 -0
  52. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/bundles.py +0 -0
  53. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/capsules.py +0 -0
  54. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/comparison.py +0 -0
  55. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/console.py +0 -0
  56. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/decisions.py +0 -0
  57. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/diagnostics.py +0 -0
  58. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/__init__.py +0 -0
  59. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/analyzer.py +0 -0
  60. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/api.py +0 -0
  61. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/candidate.py +0 -0
  62. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/catalog.py +0 -0
  63. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/catalog_build.py +0 -0
  64. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/catalog_cli.py +0 -0
  65. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/catalog_manifest.py +0 -0
  66. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/data/catalog.json +0 -0
  67. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/defaults.py +0 -0
  68. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/engine.py +0 -0
  69. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/errors.py +0 -0
  70. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/module_inventory.py +0 -0
  71. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/planner.py +0 -0
  72. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/policy.py +0 -0
  73. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/probe.py +0 -0
  74. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/py.typed +0 -0
  75. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/result.py +0 -0
  76. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/schema_resources.py +0 -0
  77. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/schemas/catalog-v1.schema.json +0 -0
  78. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/schemas/plan-v1.schema.json +0 -0
  79. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/discovery/schemas/result-v1.schema.json +0 -0
  80. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/environments.py +0 -0
  81. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/errors.py +0 -0
  82. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/events.py +0 -0
  83. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/facade.py +0 -0
  84. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/frontmatter.py +0 -0
  85. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/health.py +0 -0
  86. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/lake.py +0 -0
  87. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/lockfiles.py +0 -0
  88. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/locking.py +0 -0
  89. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/matrix.py +0 -0
  90. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/models.py +0 -0
  91. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/oci.py +0 -0
  92. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/packs.py +0 -0
  93. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/policies.py +0 -0
  94. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/profiling.py +0 -0
  95. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/programs.py +0 -0
  96. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/projects.py +0 -0
  97. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/publisher_verification.py +0 -0
  98. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/py.typed +0 -0
  99. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/references.py +0 -0
  100. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/resolver.py +0 -0
  101. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/run_cli.py +0 -0
  102. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/schema_resources.py +0 -0
  103. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/serialization.py +0 -0
  104. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/specs.py +0 -0
  105. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/store.py +0 -0
  106. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/timings.py +0 -0
  107. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/toolchain_oci.py +0 -0
  108. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/toolchain_slim.py +0 -0
  109. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/toolchains.py +0 -0
  110. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/verification.py +0 -0
  111. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime/wire.py +0 -0
  112. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime.egg-info/SOURCES.txt +0 -0
  113. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime.egg-info/dependency_links.txt +0 -0
  114. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime.egg-info/entry_points.txt +0 -0
  115. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime.egg-info/requires.txt +0 -0
  116. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/lean_runtime.egg-info/top_level.txt +0 -0
  117. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/mkdocs.yml +0 -0
  118. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/schemas/cleanup-v1.schema.json +0 -0
  119. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/schemas/comparison-v1.schema.json +0 -0
  120. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/schemas/execution-v1.schema.json +0 -0
  121. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/schemas/inspect-v1.schema.json +0 -0
  122. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/schemas/matrix-v1.schema.json +0 -0
  123. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/schemas/profile-v1.schema.json +0 -0
  124. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/schemas/verify-v1.schema.json +0 -0
  125. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/scripts/announcement_gate.py +0 -0
  126. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/scripts/registry_preflight.py +0 -0
  127. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/scripts/run_compatibility.py +0 -0
  128. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/scripts/run_v1_case_study.py +0 -0
  129. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/scripts/smoke_wheel.py +0 -0
  130. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/setup.cfg +0 -0
  131. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/conftest.py +0 -0
  132. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/discovery/e2e/test_real_discovery.py +0 -0
  133. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/discovery/test_analyzer.py +0 -0
  134. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/discovery/test_catalog.py +0 -0
  135. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/discovery/test_catalog_build.py +0 -0
  136. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/discovery/test_discovery_schema_resources.py +0 -0
  137. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/discovery/test_engine.py +0 -0
  138. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/discovery/test_planner.py +0 -0
  139. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/discovery/test_policy.py +0 -0
  140. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_backend.py +0 -0
  141. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_bundles.py +0 -0
  142. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_capsules.py +0 -0
  143. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_console.py +0 -0
  144. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_diagnostics.py +0 -0
  145. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_environment_integration.py +0 -0
  146. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_events_health.py +0 -0
  147. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_facade.py +0 -0
  148. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_frontmatter.py +0 -0
  149. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_git.py +0 -0
  150. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_interactive.py +0 -0
  151. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_lockfiles.py +0 -0
  152. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_models.py +0 -0
  153. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_multifile.py +0 -0
  154. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_packs.py +0 -0
  155. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_paths.py +0 -0
  156. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_prebuilt_policy.py +0 -0
  157. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_project_publish.py +0 -0
  158. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_references.py +0 -0
  159. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_run_cli.py +0 -0
  160. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_runtime.py +0 -0
  161. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_schema_resources.py +0 -0
  162. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_schemas.py +0 -0
  163. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_signatures.py +0 -0
  164. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_specs.py +0 -0
  165. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_store.py +0 -0
  166. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_toolchain_oci.py +0 -0
  167. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_toolchain_slim.py +0 -0
  168. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_toolchains.py +0 -0
  169. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_v1_precision.py +0 -0
  170. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_verification_inventory.py +0 -0
  171. {lean_runtime-2.6.0 → lean_runtime-2.6.2}/tests/test_verification_probe.py +0 -0
@@ -2,6 +2,32 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 2.6.2 - 2026-08-14
6
+
7
+ - Keep an existing initialization target's directory inode alive, so running
8
+ `lean-runtime init .` no longer strands the invoking shell in an unlinked
9
+ working directory. Fully prepared staged children are published with rollback
10
+ while Git metadata, custom agent guidance, and index state remain in place.
11
+ - Make attachment plans distinguish checkout bytes removed, compatible shared
12
+ bytes already available, new shared bytes required, and estimated machine
13
+ recovery. Exact managed packages now bypass redundant source resolution when
14
+ their toolchain, platform, revision, and effective dependency closure match.
15
+ - Emit package-by-package shared-workspace resolution, reuse, and import events,
16
+ and add `lean-runtime --version`.
17
+
18
+ ## 2.6.1 - 2026-08-14
19
+
20
+ - Allow `lean-runtime init .` at an otherwise empty Git repository root while
21
+ preserving its original `.git` directory or worktree file, HEAD, and index.
22
+ Planning and execution now share target validation, so `init --plan` rejects
23
+ unsupported existing contents before reporting an acquisition-ready plan.
24
+ - Add `init --name NAME` for repository directories whose filesystem spelling
25
+ should not determine the Lake package and Lean root module name. Repeating
26
+ the same named initialization is idempotent; a conflicting name is rejected.
27
+ - Make `init --plan --offline` and `update --plan --offline` genuinely
28
+ network-free. Plans now report explicit blockers and return a failing status
29
+ when an exact local graph or required full toolchain is unavailable.
30
+
5
31
  ## 2.6.0 - 2026-08-14
6
32
 
7
33
  - 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.2
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,14 @@ 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
+ An existing target directory remains the same live directory, so `init .` does
217
+ not invalidate the invoking shell's working directory.
218
+ When the directory spelling is not the intended Lean module capitalization,
219
+ set it explicitly, for example `lean-runtime init . --name IntegralFramework`.
212
220
 
213
221
  One-shot helpers are available when setup reuse is unnecessary:
214
222
 
@@ -173,6 +173,14 @@ 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
+ An existing target directory remains the same live directory, so `init .` does
181
+ not invalidate the invoking shell's working directory.
182
+ When the directory spelling is not the intended Lean module capitalization,
183
+ set it explicitly, for example `lean-runtime init . --name IntegralFramework`.
176
184
 
177
185
  One-shot helpers are available when setup reuse is unnecessary:
178
186
 
@@ -176,6 +176,21 @@ recursive mode inventories a project tree, reports blockers and estimated disk
176
176
  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
+ The attachment preview separates bytes removed from each checkout, compatible
180
+ bytes already present in the shared store, new shared bytes that must be
181
+ imported, and estimated machine-level recovery. These quantities differ on a
182
+ first attachment because copy-on-write migration can shrink the repository
183
+ without immediately freeing the dependency blocks retained by the shared copy.
184
+
185
+ A new `init` target may be absent, empty, or an otherwise empty Git root, with
186
+ an optional existing `AGENTS.md`. Git metadata and the custom guide are
187
+ preserved. Planning rejects any other existing contents using the same
188
+ validation as execution.
189
+ Existing target directories are populated in place after staging verifies, so
190
+ `lean-runtime init .` keeps the caller's current working directory valid.
191
+ `--name NAME` overrides the inferred Lake package/root module name for a new
192
+ project, which is useful when a lowercase repository name needs internal
193
+ capitalization such as `IntegralFramework`.
179
194
 
180
195
  Add supporting source files with repeatable `--include` options:
181
196
 
@@ -37,10 +37,29 @@ 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
+ If the target already exists, initialization keeps that directory inode alive;
52
+ the shell that runs `lean-runtime init .` can immediately continue using it.
53
+ The directory name normally supplies the Lake package/root module name. Override
54
+ it when capitalization matters, for example:
55
+
56
+ ```bash
57
+ lean-runtime init . --name IntegralFramework
58
+ ```
59
+
60
+ The same command can be rerun safely after initialization; a different name is
61
+ rejected rather than silently renaming an existing project.
62
+
44
63
  Dependency upgrades are explicit:
45
64
 
46
65
  ```bash
@@ -61,11 +61,23 @@ 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—stays in place without changing HEAD, the
73
+ index, or remotes. The existing directory inode also stays live, so invoking
74
+ `lean-runtime init .` does not strand the shell in an unlinked working
75
+ directory. A pre-existing `AGENTS.md` is preserved. Other contents must be moved
76
+ first or represented by an existing pinned Lake project; the read-only plan
77
+ enforces the same rule as execution.
78
+ Use `--name NAME` when creating inside a lowercase or otherwise differently
79
+ named repository and the Lean root module needs an explicit spelling.
80
+
69
81
  For an existing pinned project, `lean-runtime init .` adopts its current
70
82
  manifest without changing versions. Register a collection once with
71
83
  `lean-runtime scan ~/research`; future exact matches are preferred over
@@ -96,13 +108,15 @@ lean-runtime attach ~/research --recursive
96
108
  lean-runtime attach ~/research --recursive --execute
97
109
  ```
98
110
 
99
- The preview groups exact graphs, reports current and estimated shared storage,
100
- and identifies missing local paths, dirty dependencies, and revision
101
- mismatches. Execution continues through independent projects while reporting
102
- per-project failures. It first prepares the exact shared workspace and probes it
103
- through Lake. Only then does it atomically replace `.lake/packages` with package
104
- links, probe the resulting project through ordinary Lake, and discard the old
105
- generated copies. Any failure restores the original package directory.
111
+ The preview groups exact graphs and separately reports checkout bytes removed,
112
+ compatible shared bytes already ready, new shared bytes required, and estimated
113
+ machine-level recovery. It also identifies missing local paths, dirty
114
+ dependencies, and revision mismatches. Execution continues through independent
115
+ projects while reporting per-project failures. It first prepares the exact
116
+ shared workspace and probes it through Lake. Only then does it atomically
117
+ replace `.lake/packages` with package links, probe the resulting project through
118
+ ordinary Lake, and discard the old generated copies. Any failure restores the
119
+ original package directory.
106
120
 
107
121
  The project itself remains portable. To return to independent package copies:
108
122
 
@@ -141,12 +155,20 @@ their artifacts.
141
155
 
142
156
  The first shared build imports a clean, revision-matching local dependency copy
143
157
  when one exists, using copy-on-write filesystem clones where supported. Missing
144
- sources are fetched at the exact manifest commit. A plain shared build never
158
+ sources are fetched at the exact manifest commit. A later project bypasses
159
+ source resolution when a managed package marker proves that its toolchain,
160
+ platform, revision, and effective dependency closure already match. A plain shared build never
145
161
  deletes the old `.lake/packages`; only an explicit `attach --execute` replaces
146
162
  those generated copies after verification. `lean-runtime storage` reports
147
163
  shared project package usage; automatic cleanup of those packages is not yet
148
164
  implemented.
149
165
 
166
+ Check capsules and mutable shared packages are intentionally separate storage
167
+ tiers. Capsules are source-free and trimmed for checking; ordinary Lake
168
+ development needs source-shaped packages and build metadata. A capsule can seed
169
+ compatible build artifacts while the exact source workspace is materialized,
170
+ but attached projects ultimately link to the mutable shared-project store.
171
+
150
172
  This mode requires a manifest and never runs `lake update`, so it cannot silently
151
173
  change dependency revisions. It also reuses an existing local Git object database
152
174
  when that database contains another requested revision, avoiding redundant Git
@@ -7,12 +7,13 @@ import json
7
7
  import sys
8
8
  import time
9
9
  from datetime import datetime
10
+ from importlib.metadata import version as distribution_version
10
11
  from pathlib import Path
11
12
  from typing import Any
12
13
 
13
14
  from .console import styler_for
14
15
  from .environments import ExecutionCapture
15
- from .errors import LeanRuntimeError, MaterializationError, ResolutionError
16
+ from .errors import LeanRuntimeError, MaterializationError, ProjectError, ResolutionError
16
17
  from .events import RuntimeEvent
17
18
  from .lake import ROOT_MODULE
18
19
  from .lockfiles import EnvironmentLock
@@ -172,9 +173,12 @@ def _render_adoption_plan(plan: AdoptionPlan) -> None:
172
173
  for warning in project.warnings:
173
174
  print(f" note: {warning}")
174
175
  print()
175
- print(f"Current dependency copies: {format_byte_size(plan.current_dependency_bytes)}")
176
- print(f"Estimated shared copies: {format_byte_size(plan.estimated_shared_bytes)}")
177
- print(f"Potential recovery: {format_byte_size(plan.estimated_reclaimable_bytes)}")
176
+ print(f"Checkout bytes removed: {format_byte_size(plan.checkout_bytes_removed)}")
177
+ print(f"Shared bytes already ready:{format_byte_size(plan.shared_bytes_reused):>10}")
178
+ print(f"New shared bytes needed: {format_byte_size(plan.new_shared_bytes)}")
179
+ print(
180
+ f"Estimated machine recovery: {format_byte_size(plan.estimated_machine_reclaimable_bytes)}"
181
+ )
178
182
 
179
183
 
180
184
  def _render_init_plan(plan: ProjectInitPlan) -> None:
@@ -184,7 +188,8 @@ def _render_init_plan(plan: ProjectInitPlan) -> None:
184
188
  print(f"Exact dependencies: {len(plan.packages)} package(s); versions unchanged")
185
189
  return
186
190
  context = f"Mathlib {plan.mathlib_version}" if plan.mathlib_version else "core Lean"
187
- print(f"Create {plan.root.name} · {context} · {plan.toolchain}")
191
+ name = plan.project_name or plan.root.name
192
+ print(f"Create {name} in {plan.root} · {context} · {plan.toolchain}")
188
193
  if not plan.toolchain_installed:
189
194
  print("Full Lake toolchain: download required (size not published by Elan)")
190
195
  if plan.seed_root is not None:
@@ -194,6 +199,8 @@ def _render_init_plan(plan: ProjectInitPlan) -> None:
194
199
  print(f"Download: {format_byte_size(plan.download_bytes)}")
195
200
  else:
196
201
  print("Download: unknown (no compatible published artifact could be priced)")
202
+ for blocker in plan.blockers:
203
+ print(f"Blocker: {blocker}")
197
204
 
198
205
 
199
206
  def _render_update_plan(plan: ProjectUpdatePlan) -> None:
@@ -277,6 +284,11 @@ def _add_policy(parser: argparse.ArgumentParser, *, timeout: float = 120) -> Non
277
284
 
278
285
  def parser() -> argparse.ArgumentParser:
279
286
  root = argparse.ArgumentParser(prog="lean-runtime")
287
+ root.add_argument(
288
+ "--version",
289
+ action="version",
290
+ version=f"%(prog)s {distribution_version('lean-runtime')}",
291
+ )
280
292
  root.add_argument("--home", help="runtime store root")
281
293
  root.add_argument("--quiet", action="store_true", help="suppress progress events")
282
294
  root.add_argument("--verbose", action="store_true", help="show detailed decisions and checks")
@@ -538,6 +550,7 @@ def parser() -> argparse.ArgumentParser:
538
550
  "init", help="create a latest-Mathlib project or adopt an existing Lake project"
539
551
  )
540
552
  init.add_argument("path", type=Path, nargs="?", default=Path("."))
553
+ init.add_argument("--name", help="explicit Lake package and root module name")
541
554
  init_context = init.add_mutually_exclusive_group()
542
555
  init_context.add_argument(
543
556
  "--mathlib",
@@ -666,6 +679,7 @@ def main(argv: list[str] | None = None) -> int:
666
679
  if args.command == "init":
667
680
  init_plan = runtime.plan_project_init(
668
681
  args.path,
682
+ name=args.name,
669
683
  mathlib=args.mathlib,
670
684
  toolchain=args.toolchain,
671
685
  seed_from=args.seed_from,
@@ -675,11 +689,16 @@ def main(argv: list[str] | None = None) -> int:
675
689
  _json(init_plan.to_dict())
676
690
  else:
677
691
  _render_init_plan(init_plan)
678
- return 0
692
+ return 0 if init_plan.ready else 1
693
+ if not init_plan.ready:
694
+ raise ProjectError(
695
+ "project cannot be initialized:\n- " + "\n- ".join(init_plan.blockers)
696
+ )
679
697
  if not args.json:
680
698
  _render_init_plan(init_plan)
681
699
  init_result = runtime.init_project(
682
700
  args.path,
701
+ name=args.name,
683
702
  mathlib=args.mathlib,
684
703
  toolchain=args.toolchain,
685
704
  agents=args.agents,
@@ -737,7 +756,11 @@ def main(argv: list[str] | None = None) -> int:
737
756
  _render_adoption_plan(adoption_plan)
738
757
  print("No changes made. Re-run with --execute to adopt shared packages.")
739
758
  return 0 if adoption_plan.blocked == 0 else 1
740
- adoption_result = runtime.attach_projects(args.path, recursive=args.recursive)
759
+ adoption_result = runtime.attach_projects(
760
+ args.path,
761
+ recursive=args.recursive,
762
+ plan=adoption_plan,
763
+ )
741
764
  if args.json:
742
765
  _json(adoption_result.to_dict())
743
766
  else:
@@ -156,6 +156,8 @@ class AdoptionPlan:
156
156
  recursive: bool
157
157
  current_dependency_bytes: int
158
158
  estimated_shared_bytes: int
159
+ shared_bytes_reused: int = 0
160
+ new_shared_bytes: int = 0
159
161
 
160
162
  @property
161
163
  def ready(self) -> int:
@@ -167,7 +169,16 @@ class AdoptionPlan:
167
169
 
168
170
  @property
169
171
  def estimated_reclaimable_bytes(self) -> int:
170
- return max(0, self.current_dependency_bytes - self.estimated_shared_bytes)
172
+ """Compatibility alias for estimated machine-level recovery."""
173
+ return self.estimated_machine_reclaimable_bytes
174
+
175
+ @property
176
+ def checkout_bytes_removed(self) -> int:
177
+ return self.current_dependency_bytes
178
+
179
+ @property
180
+ def estimated_machine_reclaimable_bytes(self) -> int:
181
+ return max(0, self.current_dependency_bytes - self.new_shared_bytes)
171
182
 
172
183
  def to_dict(self) -> dict[str, Any]:
173
184
  return {
@@ -177,6 +188,10 @@ class AdoptionPlan:
177
188
  "blocked": self.blocked,
178
189
  "current_dependency_bytes": self.current_dependency_bytes,
179
190
  "estimated_shared_bytes": self.estimated_shared_bytes,
191
+ "checkout_bytes_removed": self.checkout_bytes_removed,
192
+ "shared_bytes_reused": self.shared_bytes_reused,
193
+ "new_shared_bytes": self.new_shared_bytes,
194
+ "estimated_machine_reclaimable_bytes": (self.estimated_machine_reclaimable_bytes),
180
195
  "estimated_reclaimable_bytes": self.estimated_reclaimable_bytes,
181
196
  }
182
197
 
@@ -232,6 +247,12 @@ class ProjectInitPlan:
232
247
  download_bytes_complete: bool
233
248
  already_attached: bool = False
234
249
  toolchain_installed: bool = False
250
+ project_name: str | None = None
251
+ blockers: tuple[str, ...] = ()
252
+
253
+ @property
254
+ def ready(self) -> bool:
255
+ return not self.blockers
235
256
 
236
257
  def to_dict(self) -> dict[str, Any]:
237
258
  return {
@@ -245,6 +266,9 @@ class ProjectInitPlan:
245
266
  "download_bytes_complete": self.download_bytes_complete,
246
267
  "already_attached": self.already_attached,
247
268
  "toolchain_installed": self.toolchain_installed,
269
+ "project_name": self.project_name,
270
+ "ready": self.ready,
271
+ "blockers": list(self.blockers),
248
272
  }
249
273
 
250
274
 
@@ -393,10 +417,16 @@ def inspect_adoption(root: Path) -> ProjectAdoption:
393
417
  )
394
418
 
395
419
 
396
- def plan_adoption(path: Path, *, recursive: bool) -> AdoptionPlan:
420
+ def plan_adoption(
421
+ path: Path,
422
+ *,
423
+ recursive: bool,
424
+ shared: SharedProjectManager | None = None,
425
+ ) -> AdoptionPlan:
397
426
  roots = discover_shareable_projects(path, recursive=recursive)
398
427
  projects = tuple(inspect_adoption(root) for root in roots)
399
428
  groups: dict[str, int] = {}
429
+ reused_groups: set[str] = set()
400
430
  for project in projects:
401
431
  if not project.ready or project.attached:
402
432
  continue
@@ -408,6 +438,15 @@ def plan_adoption(path: Path, *, recursive: bool) -> AdoptionPlan:
408
438
  str(entry["name"]): _entry_identity(identity_entry)
409
439
  for entry, identity_entry in zip(entries, identity_entries, strict=True)
410
440
  }
441
+ reusable = (
442
+ shared.reusable_packages(
443
+ context,
444
+ entries,
445
+ effective_entries=effective_entries,
446
+ )
447
+ if shared is not None
448
+ else {}
449
+ )
411
450
  package_dir = _packages_directory(context)
412
451
  for entry in entries:
413
452
  if entry["type"] != "git":
@@ -416,23 +455,30 @@ def plan_adoption(path: Path, *, recursive: bool) -> AdoptionPlan:
416
455
  local_bytes = _tree_bytes(local)
417
456
  subdir = entry.get("subDir")
418
457
  source_package = local / subdir if isinstance(subdir, str) and subdir else local
419
- if _git_head(local) == entry.get("rev") and source_package.is_dir():
458
+ reusable_package = reusable.get(str(entry["name"]))
459
+ if reusable_package is not None:
460
+ key = reusable_package.name
461
+ reused_groups.add(key)
462
+ elif _git_head(local) == entry.get("rev") and source_package.is_dir():
420
463
  identity = _package_identity(
421
464
  context=context,
422
465
  entry=entry,
423
466
  source_package=source_package,
424
467
  effective_entries=effective_entries,
425
468
  )
469
+ key = sha256_id("project_package", identity)
426
470
  else:
427
471
  identity = {
428
472
  "toolchain": context.toolchain,
429
473
  "manifest": manifest,
430
474
  "package": entry,
431
475
  }
432
- key = sha256_id("project_adoption_estimate", identity)
476
+ key = sha256_id("project_adoption_estimate", identity)
433
477
  groups[key] = max(groups.get(key, 0), local_bytes)
434
478
  current = sum(project.dependency_bytes for project in projects if not project.attached)
435
- return AdoptionPlan(projects, recursive, current, sum(groups.values()))
479
+ reused = sum(size for key, size in groups.items() if key in reused_groups)
480
+ new = sum(size for key, size in groups.items() if key not in reused_groups)
481
+ return AdoptionPlan(projects, recursive, current, sum(groups.values()), reused, new)
436
482
 
437
483
 
438
484
  def plan_detachment(root: Path) -> DetachmentPlan:
@@ -490,6 +536,12 @@ class ProjectAdopter:
490
536
  seed_packages=seed_packages,
491
537
  seed_package_paths=seed_package_paths,
492
538
  )
539
+ self.shared.events.emit(
540
+ "project.attach.shared_probe_started",
541
+ f"Verifying shared graph for {context.root.name}",
542
+ phase="project-attach",
543
+ packages=len(workspace.package_ids),
544
+ )
493
545
  probe(workspace.overrides_file)
494
546
  manifest_packages = _manifest_packages(context)
495
547
  override_value = json.loads(workspace.overrides_file.read_text(encoding="utf-8"))
@@ -516,6 +568,13 @@ class ProjectAdopter:
516
568
  marker = lake_dir / ATTACHMENT_RECORD
517
569
  config = context.root / PROJECT_CONFIG
518
570
  try:
571
+ self.shared.events.emit(
572
+ "project.attach.swap_started",
573
+ f"Replacing local dependency copies for {context.root.name}",
574
+ phase="project-attach",
575
+ packages=len(workspace.package_ids),
576
+ checkout_bytes=reclaimed,
577
+ )
519
578
  for entry in manifest_packages:
520
579
  if entry["type"] != "git":
521
580
  continue
@@ -535,6 +594,12 @@ class ProjectAdopter:
535
594
  packages_dir.replace(backup)
536
595
  staging.replace(packages_dir)
537
596
  swapped = True
597
+ self.shared.events.emit(
598
+ "project.attach.attached_probe_started",
599
+ f"Verifying attached graph for {context.root.name}",
600
+ phase="project-attach",
601
+ packages=len(workspace.package_ids),
602
+ )
538
603
  probe(None)
539
604
  record = {
540
605
  "schema": ATTACHMENT_SCHEMA,
@@ -547,6 +612,13 @@ class ProjectAdopter:
547
612
  write_json_atomic(marker, record)
548
613
  config.write_text(_CONFIG_CONTENT, encoding="utf-8")
549
614
  _remove_path(backup)
615
+ self.shared.events.emit(
616
+ "project.attach.completed",
617
+ f"Attached shared dependencies for {context.root.name}",
618
+ phase="project-attach",
619
+ packages=len(workspace.package_ids),
620
+ checkout_bytes=reclaimed,
621
+ )
550
622
  except BaseException:
551
623
  _remove_path(staging)
552
624
  if swapped: