lean-runtime 2.6.1__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.1 → lean_runtime-2.6.2}/CHANGELOG.md +13 -0
  2. {lean_runtime-2.6.1/lean_runtime.egg-info → lean_runtime-2.6.2}/PKG-INFO +3 -1
  3. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/README.md +2 -0
  4. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/cli.md +7 -0
  5. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/getting-started.md +2 -0
  6. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/local-projects.md +24 -12
  7. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/cli.py +17 -4
  8. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/project_sharing.py +68 -5
  9. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/runtime.py +45 -31
  10. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/shared_projects.py +123 -15
  11. {lean_runtime-2.6.1 → lean_runtime-2.6.2/lean_runtime.egg-info}/PKG-INFO +3 -1
  12. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/pyproject.toml +1 -1
  13. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_cli.py +7 -0
  14. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_projects.py +58 -0
  15. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/LICENSE +0 -0
  16. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/MANIFEST.in +0 -0
  17. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/catalog/environments.toml +0 -0
  18. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/catalog/locks/core-v4.32.2.lock.json +0 -0
  19. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/catalog/locks/leancert-v4.30.0.5.lock.json +0 -0
  20. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/catalog/locks/leancert-v4.31.0.lock.json +0 -0
  21. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/catalog/locks/leancert-v4.32.2.4.lock.json +0 -0
  22. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/catalog/locks/leancert-v4.33.0.lock.json +0 -0
  23. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/catalog/locks/mathlib-v4.30.0.lock.json +0 -0
  24. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/catalog/locks/mathlib-v4.31.0.lock.json +0 -0
  25. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/catalog/locks/mathlib-v4.32.2.lock.json +0 -0
  26. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/catalog/locks/mathlib-v4.33.0.lock.json +0 -0
  27. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/compatibility/README.md +0 -0
  28. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/compatibility/mathlib-4.32.2.json +0 -0
  29. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/compatibility/mathlib-4.32.2.toml +0 -0
  30. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/compatibility/mathlib-4.33.0.json +0 -0
  31. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/compatibility/mathlib-4.33.0.toml +0 -0
  32. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/architecture.md +0 -0
  33. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/captures.md +0 -0
  34. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/case-study-v1.md +0 -0
  35. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/development.md +0 -0
  36. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/environments.md +0 -0
  37. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/index.md +0 -0
  38. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/portable-copies.md +0 -0
  39. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/project-publishing.md +0 -0
  40. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/python-api.md +0 -0
  41. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/ready-programs.md +0 -0
  42. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/standalone-files.md +0 -0
  43. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/trust-and-limitations.md +0 -0
  44. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/docs/v1-precision.md +0 -0
  45. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/examples/mathlib.toml +0 -0
  46. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/__init__.py +0 -0
  47. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/__main__.py +0 -0
  48. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/_git.py +0 -0
  49. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/_paths.py +0 -0
  50. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/backends.py +0 -0
  51. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/bundles.py +0 -0
  52. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/capsules.py +0 -0
  53. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/comparison.py +0 -0
  54. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/console.py +0 -0
  55. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/decisions.py +0 -0
  56. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/diagnostics.py +0 -0
  57. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/__init__.py +0 -0
  58. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/analyzer.py +0 -0
  59. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/api.py +0 -0
  60. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/candidate.py +0 -0
  61. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/catalog.py +0 -0
  62. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/catalog_build.py +0 -0
  63. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/catalog_cli.py +0 -0
  64. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/catalog_manifest.py +0 -0
  65. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/data/catalog.json +0 -0
  66. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/defaults.py +0 -0
  67. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/engine.py +0 -0
  68. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/errors.py +0 -0
  69. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/module_inventory.py +0 -0
  70. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/planner.py +0 -0
  71. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/policy.py +0 -0
  72. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/probe.py +0 -0
  73. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/py.typed +0 -0
  74. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/result.py +0 -0
  75. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/schema_resources.py +0 -0
  76. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/schemas/catalog-v1.schema.json +0 -0
  77. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/schemas/plan-v1.schema.json +0 -0
  78. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/discovery/schemas/result-v1.schema.json +0 -0
  79. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/environments.py +0 -0
  80. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/errors.py +0 -0
  81. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/events.py +0 -0
  82. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/facade.py +0 -0
  83. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/frontmatter.py +0 -0
  84. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/health.py +0 -0
  85. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/lake.py +0 -0
  86. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/lockfiles.py +0 -0
  87. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/locking.py +0 -0
  88. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/matrix.py +0 -0
  89. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/models.py +0 -0
  90. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/oci.py +0 -0
  91. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/packs.py +0 -0
  92. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/policies.py +0 -0
  93. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/profiling.py +0 -0
  94. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/programs.py +0 -0
  95. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/projects.py +0 -0
  96. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/publisher_verification.py +0 -0
  97. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/py.typed +0 -0
  98. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/references.py +0 -0
  99. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/resolver.py +0 -0
  100. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/run_cli.py +0 -0
  101. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/schema_resources.py +0 -0
  102. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/serialization.py +0 -0
  103. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/specs.py +0 -0
  104. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/store.py +0 -0
  105. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/timings.py +0 -0
  106. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/toolchain_oci.py +0 -0
  107. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/toolchain_slim.py +0 -0
  108. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/toolchains.py +0 -0
  109. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/verification.py +0 -0
  110. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime/wire.py +0 -0
  111. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime.egg-info/SOURCES.txt +0 -0
  112. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime.egg-info/dependency_links.txt +0 -0
  113. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime.egg-info/entry_points.txt +0 -0
  114. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime.egg-info/requires.txt +0 -0
  115. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/lean_runtime.egg-info/top_level.txt +0 -0
  116. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/mkdocs.yml +0 -0
  117. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/schemas/cleanup-v1.schema.json +0 -0
  118. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/schemas/comparison-v1.schema.json +0 -0
  119. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/schemas/execution-v1.schema.json +0 -0
  120. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/schemas/inspect-v1.schema.json +0 -0
  121. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/schemas/matrix-v1.schema.json +0 -0
  122. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/schemas/profile-v1.schema.json +0 -0
  123. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/schemas/verify-v1.schema.json +0 -0
  124. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/scripts/announcement_gate.py +0 -0
  125. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/scripts/registry_preflight.py +0 -0
  126. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/scripts/run_compatibility.py +0 -0
  127. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/scripts/run_v1_case_study.py +0 -0
  128. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/scripts/smoke_wheel.py +0 -0
  129. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/setup.cfg +0 -0
  130. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/conftest.py +0 -0
  131. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/discovery/e2e/test_real_discovery.py +0 -0
  132. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/discovery/test_analyzer.py +0 -0
  133. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/discovery/test_catalog.py +0 -0
  134. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/discovery/test_catalog_build.py +0 -0
  135. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/discovery/test_discovery_schema_resources.py +0 -0
  136. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/discovery/test_engine.py +0 -0
  137. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/discovery/test_planner.py +0 -0
  138. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/discovery/test_policy.py +0 -0
  139. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_backend.py +0 -0
  140. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_bundles.py +0 -0
  141. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_capsules.py +0 -0
  142. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_console.py +0 -0
  143. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_diagnostics.py +0 -0
  144. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_environment_integration.py +0 -0
  145. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_events_health.py +0 -0
  146. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_facade.py +0 -0
  147. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_frontmatter.py +0 -0
  148. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_git.py +0 -0
  149. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_interactive.py +0 -0
  150. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_lockfiles.py +0 -0
  151. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_models.py +0 -0
  152. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_multifile.py +0 -0
  153. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_packs.py +0 -0
  154. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_paths.py +0 -0
  155. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_prebuilt_policy.py +0 -0
  156. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_programs.py +0 -0
  157. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_project_publish.py +0 -0
  158. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_references.py +0 -0
  159. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_run_cli.py +0 -0
  160. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_runtime.py +0 -0
  161. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_schema_resources.py +0 -0
  162. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_schemas.py +0 -0
  163. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_signatures.py +0 -0
  164. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_specs.py +0 -0
  165. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_store.py +0 -0
  166. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_toolchain_oci.py +0 -0
  167. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_toolchain_slim.py +0 -0
  168. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_toolchains.py +0 -0
  169. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_v1_precision.py +0 -0
  170. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_verification_inventory.py +0 -0
  171. {lean_runtime-2.6.1 → lean_runtime-2.6.2}/tests/test_verification_probe.py +0 -0
@@ -2,6 +2,19 @@
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
+
5
18
  ## 2.6.1 - 2026-08-14
6
19
 
7
20
  - Allow `lean-runtime init .` at an otherwise empty Git repository root while
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-runtime
3
- Version: 2.6.1
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
@@ -213,6 +213,8 @@ For a new project, the target may be absent, empty, or an otherwise empty Git
213
213
  repository root; existing Git identity and index state are preserved. A custom
214
214
  `AGENTS.md` is also allowed and retained. Other existing contents are rejected
215
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.
216
218
  When the directory spelling is not the intended Lean module capitalization,
217
219
  set it explicitly, for example `lean-runtime init . --name IntegralFramework`.
218
220
 
@@ -177,6 +177,8 @@ For a new project, the target may be absent, empty, or an otherwise empty Git
177
177
  repository root; existing Git identity and index state are preserved. A custom
178
178
  `AGENTS.md` is also allowed and retained. Other existing contents are rejected
179
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.
180
182
  When the directory spelling is not the intended Lean module capitalization,
181
183
  set it explicitly, for example `lean-runtime init . --name IntegralFramework`.
182
184
 
@@ -176,11 +176,18 @@ 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.
179
184
 
180
185
  A new `init` target may be absent, empty, or an otherwise empty Git root, with
181
186
  an optional existing `AGENTS.md`. Git metadata and the custom guide are
182
187
  preserved. Planning rejects any other existing contents using the same
183
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.
184
191
  `--name NAME` overrides the inferred Lake package/root module name for a new
185
192
  project, which is useful when a lowercase repository name needs internal
186
193
  capitalization such as `IntegralFramework`.
@@ -48,6 +48,8 @@ empty Git repository root. In the Git case, `init` preserves the existing HEAD,
48
48
  index, remotes, and worktree metadata. It also preserves an existing
49
49
  `AGENTS.md`; any other non-project contents are rejected by both `--plan` and
50
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.
51
53
  The directory name normally supplies the Lake package/root module name. Override
52
54
  it when capitalization matters, for example:
53
55
 
@@ -69,10 +69,12 @@ toolchain is rejected under `--offline` or `--max-download` rather than silently
69
69
  bypassing the policy.
70
70
 
71
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.
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.
76
78
  Use `--name NAME` when creating inside a lowercase or otherwise differently
77
79
  named repository and the Lean root module needs an explicit spelling.
78
80
 
@@ -106,13 +108,15 @@ lean-runtime attach ~/research --recursive
106
108
  lean-runtime attach ~/research --recursive --execute
107
109
  ```
108
110
 
109
- The preview groups exact graphs, reports current and estimated shared storage,
110
- and identifies missing local paths, dirty dependencies, and revision
111
- mismatches. Execution continues through independent projects while reporting
112
- per-project failures. It first prepares the exact shared workspace and probes it
113
- through Lake. Only then does it atomically replace `.lake/packages` with package
114
- links, probe the resulting project through ordinary Lake, and discard the old
115
- 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.
116
120
 
117
121
  The project itself remains portable. To return to independent package copies:
118
122
 
@@ -151,12 +155,20 @@ their artifacts.
151
155
 
152
156
  The first shared build imports a clean, revision-matching local dependency copy
153
157
  when one exists, using copy-on-write filesystem clones where supported. Missing
154
- 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
155
161
  deletes the old `.lake/packages`; only an explicit `attach --execute` replaces
156
162
  those generated copies after verification. `lean-runtime storage` reports
157
163
  shared project package usage; automatic cleanup of those packages is not yet
158
164
  implemented.
159
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
+
160
172
  This mode requires a manifest and never runs `lake update`, so it cannot silently
161
173
  change dependency revisions. It also reuses an existing local Git object database
162
174
  when that database contains another requested revision, avoiding redundant Git
@@ -7,6 +7,7 @@ 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
 
@@ -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:
@@ -280,6 +284,11 @@ def _add_policy(parser: argparse.ArgumentParser, *, timeout: float = 120) -> Non
280
284
 
281
285
  def parser() -> argparse.ArgumentParser:
282
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
+ )
283
292
  root.add_argument("--home", help="runtime store root")
284
293
  root.add_argument("--quiet", action="store_true", help="suppress progress events")
285
294
  root.add_argument("--verbose", action="store_true", help="show detailed decisions and checks")
@@ -747,7 +756,11 @@ def main(argv: list[str] | None = None) -> int:
747
756
  _render_adoption_plan(adoption_plan)
748
757
  print("No changes made. Re-run with --execute to adopt shared packages.")
749
758
  return 0 if adoption_plan.blocked == 0 else 1
750
- 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
+ )
751
764
  if args.json:
752
765
  _json(adoption_result.to_dict())
753
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
 
@@ -402,10 +417,16 @@ def inspect_adoption(root: Path) -> ProjectAdoption:
402
417
  )
403
418
 
404
419
 
405
- 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:
406
426
  roots = discover_shareable_projects(path, recursive=recursive)
407
427
  projects = tuple(inspect_adoption(root) for root in roots)
408
428
  groups: dict[str, int] = {}
429
+ reused_groups: set[str] = set()
409
430
  for project in projects:
410
431
  if not project.ready or project.attached:
411
432
  continue
@@ -417,6 +438,15 @@ def plan_adoption(path: Path, *, recursive: bool) -> AdoptionPlan:
417
438
  str(entry["name"]): _entry_identity(identity_entry)
418
439
  for entry, identity_entry in zip(entries, identity_entries, strict=True)
419
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
+ )
420
450
  package_dir = _packages_directory(context)
421
451
  for entry in entries:
422
452
  if entry["type"] != "git":
@@ -425,23 +455,30 @@ def plan_adoption(path: Path, *, recursive: bool) -> AdoptionPlan:
425
455
  local_bytes = _tree_bytes(local)
426
456
  subdir = entry.get("subDir")
427
457
  source_package = local / subdir if isinstance(subdir, str) and subdir else local
428
- 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():
429
463
  identity = _package_identity(
430
464
  context=context,
431
465
  entry=entry,
432
466
  source_package=source_package,
433
467
  effective_entries=effective_entries,
434
468
  )
469
+ key = sha256_id("project_package", identity)
435
470
  else:
436
471
  identity = {
437
472
  "toolchain": context.toolchain,
438
473
  "manifest": manifest,
439
474
  "package": entry,
440
475
  }
441
- key = sha256_id("project_adoption_estimate", identity)
476
+ key = sha256_id("project_adoption_estimate", identity)
442
477
  groups[key] = max(groups.get(key, 0), local_bytes)
443
478
  current = sum(project.dependency_bytes for project in projects if not project.attached)
444
- 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)
445
482
 
446
483
 
447
484
  def plan_detachment(root: Path) -> DetachmentPlan:
@@ -499,6 +536,12 @@ class ProjectAdopter:
499
536
  seed_packages=seed_packages,
500
537
  seed_package_paths=seed_package_paths,
501
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
+ )
502
545
  probe(workspace.overrides_file)
503
546
  manifest_packages = _manifest_packages(context)
504
547
  override_value = json.loads(workspace.overrides_file.read_text(encoding="utf-8"))
@@ -525,6 +568,13 @@ class ProjectAdopter:
525
568
  marker = lake_dir / ATTACHMENT_RECORD
526
569
  config = context.root / PROJECT_CONFIG
527
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
+ )
528
578
  for entry in manifest_packages:
529
579
  if entry["type"] != "git":
530
580
  continue
@@ -544,6 +594,12 @@ class ProjectAdopter:
544
594
  packages_dir.replace(backup)
545
595
  staging.replace(packages_dir)
546
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
+ )
547
603
  probe(None)
548
604
  record = {
549
605
  "schema": ATTACHMENT_SCHEMA,
@@ -556,6 +612,13 @@ class ProjectAdopter:
556
612
  write_json_atomic(marker, record)
557
613
  config.write_text(_CONFIG_CONTENT, encoding="utf-8")
558
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
+ )
559
622
  except BaseException:
560
623
  _remove_path(staging)
561
624
  if swapped:
@@ -1167,7 +1167,7 @@ class Runtime:
1167
1167
  self, path: str | os.PathLike[str], *, recursive: bool = False
1168
1168
  ) -> AdoptionPlan:
1169
1169
  """Inspect one project or a tree without changing it."""
1170
- return plan_adoption(Path(path), recursive=recursive)
1170
+ return plan_adoption(Path(path), recursive=recursive, shared=self.shared_projects)
1171
1171
 
1172
1172
  def _probe_project_graph(
1173
1173
  self,
@@ -1211,12 +1211,20 @@ class Runtime:
1211
1211
  recursive: bool = False,
1212
1212
  seed_packages: Path | None = None,
1213
1213
  seed_package_paths: dict[str, Path] | None = None,
1214
+ plan: AdoptionPlan | None = None,
1214
1215
  ) -> AdoptionBatchResult:
1215
1216
  """Adopt exact shared dependencies after a read-only plan."""
1216
- plan = self.plan_project_adoption(path, recursive=recursive)
1217
+ if plan is not None:
1218
+ requested_roots = discover_shareable_projects(Path(path), recursive=recursive)
1219
+ planned_roots = tuple(project.root for project in plan.projects)
1220
+ if requested_roots != planned_roots or plan.recursive != recursive:
1221
+ raise ProjectError("adoption plan does not match the requested project scope")
1222
+ selected_plan = plan
1223
+ else:
1224
+ selected_plan = self.plan_project_adoption(path, recursive=recursive)
1217
1225
  results: list[AdoptionResult] = []
1218
1226
  failures: list[tuple[Path, str]] = []
1219
- for project in plan.projects:
1227
+ for project in selected_plan.projects:
1220
1228
  if project.attached:
1221
1229
  workspace_id = discover_project(project.root).provenance().workspace_id
1222
1230
  results.append(
@@ -1242,15 +1250,15 @@ class Runtime:
1242
1250
  self.project_adopter.attach(
1243
1251
  context,
1244
1252
  probe=probe,
1245
- seed_packages=seed_packages if len(plan.projects) == 1 else None,
1253
+ seed_packages=(seed_packages if len(selected_plan.projects) == 1 else None),
1246
1254
  seed_package_paths=(
1247
- seed_package_paths if len(plan.projects) == 1 else None
1255
+ seed_package_paths if len(selected_plan.projects) == 1 else None
1248
1256
  ),
1249
1257
  )
1250
1258
  )
1251
1259
  except (OSError, ProjectError) as exc:
1252
1260
  failures.append((project.root, str(exc)))
1253
- return AdoptionBatchResult(plan, tuple(results), tuple(failures))
1261
+ return AdoptionBatchResult(selected_plan, tuple(results), tuple(failures))
1254
1262
 
1255
1263
  def scan_projects(
1256
1264
  self, path: str | os.PathLike[str], *, recursive: bool = True
@@ -1791,10 +1799,10 @@ class Runtime:
1791
1799
  staging = Path(
1792
1800
  tempfile.mkdtemp(prefix=f".{target.name}.lean-runtime-init-", dir=target.parent)
1793
1801
  )
1794
- backup: Path | None = None
1795
1802
  published = False
1796
1803
  original_git = next((entry for entry in existing_entries if entry.name == ".git"), None)
1797
- git_transferred = False
1804
+ published_in_place = False
1805
+ published_entries: list[Path] = []
1798
1806
  try:
1799
1807
  custom_agents = target / "AGENTS.md"
1800
1808
  if custom_agents.is_file():
@@ -1890,41 +1898,47 @@ class Runtime:
1890
1898
  remove_tree(generated_git)
1891
1899
  elif generated_git.exists() or generated_git.is_symlink():
1892
1900
  generated_git.unlink()
1893
- original_git.replace(generated_git)
1894
- git_transferred = True
1895
1901
  if target.exists():
1896
- backup = target.parent / f".{target.name}.lean-runtime-backup-{os.getpid()}"
1897
- target.replace(backup)
1898
- staging.replace(target)
1902
+ # Keep an existing directory inode alive. Replacing the directory itself
1903
+ # leaves a shell that invoked `lean-runtime init .` inside an unlinked cwd.
1904
+ # Staging has already been fully initialized, attached, and probed, so only
1905
+ # its children need a small rollback-safe publication transaction here.
1906
+ published_in_place = True
1907
+ for entry in sorted(staging.iterdir(), key=lambda path: path.name):
1908
+ destination = target / entry.name
1909
+ if destination.exists() or destination.is_symlink():
1910
+ if entry.name == "AGENTS.md" and destination.is_file():
1911
+ if entry.is_symlink() or entry.is_file():
1912
+ entry.unlink()
1913
+ elif entry.exists():
1914
+ remove_tree(entry)
1915
+ continue
1916
+ raise ProjectError(
1917
+ f"initialization target changed while publishing: {destination}"
1918
+ )
1919
+ entry.replace(destination)
1920
+ published_entries.append(destination)
1921
+ else:
1922
+ staging.replace(target)
1899
1923
  published = True
1900
- if backup is not None:
1901
- remove_tree(backup)
1902
- backup = None
1903
1924
  context = discover_project(target)
1904
1925
  # Registry discovery is an optimization, not part of publishing the project.
1905
1926
  with suppress(OSError, EnvironmentError):
1906
1927
  self.shared_projects.remember_project(context)
1907
1928
  return replace(result.results[0], root=target)
1908
1929
  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
1916
- if published:
1917
- if target.exists():
1918
- remove_tree(target)
1919
- if backup is not None and backup.exists():
1920
- backup.replace(target)
1921
- backup = None
1930
+ if published_in_place:
1931
+ for entry in reversed(published_entries):
1932
+ if entry.is_symlink() or entry.is_file():
1933
+ entry.unlink()
1934
+ elif entry.exists():
1935
+ remove_tree(entry)
1936
+ elif published and target.exists():
1937
+ remove_tree(target)
1922
1938
  raise
1923
1939
  finally:
1924
1940
  if staging.exists():
1925
1941
  remove_tree(staging)
1926
- if backup is not None and backup.exists():
1927
- remove_tree(backup)
1928
1942
 
1929
1943
  def project(
1930
1944
  self,