lean-runtime 4.22.0__tar.gz → 4.23.0__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.
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/CHANGELOG.md +2 -0
- {lean_runtime-4.22.0/lean_runtime.egg-info → lean_runtime-4.23.0}/PKG-INFO +1 -1
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/cli.py +78 -13
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/engine.py +109 -2
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/oci.py +11 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/project_execution.py +66 -2
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/run_cli.py +12 -6
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/runtime.py +13 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0/lean_runtime.egg-info}/PKG-INFO +1 -1
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime.egg-info/SOURCES.txt +1 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/pyproject.toml +1 -1
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/discovery/test_engine.py +106 -1
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_bundles.py +3 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_cli.py +226 -1
- lean_runtime-4.23.0/tests/test_project_bootstrap.py +106 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_run_cli.py +28 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/LICENSE +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/MANIFEST.in +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/README.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/catalog/environments.toml +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/catalog/locks/core-v4.32.2.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/catalog/locks/leancert-v4.30.0.5.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/catalog/locks/leancert-v4.31.0.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/catalog/locks/leancert-v4.32.2.4.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/catalog/locks/leancert-v4.33.0.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/catalog/locks/mathlib-v4.30.0.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/catalog/locks/mathlib-v4.31.0.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/catalog/locks/mathlib-v4.32.2.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/catalog/locks/mathlib-v4.33.0.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/catalog/locks/mathlib-v4.33.1.lock.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/compatibility/README.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/compatibility/lake-artifact-cache-4.33.0.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/compatibility/mathlib-4.32.2.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/compatibility/mathlib-4.32.2.toml +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/compatibility/mathlib-4.33.0.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/compatibility/mathlib-4.33.0.toml +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/docs/concepts/context-selection.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/docs/index.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/docs/reference/commands.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/docs/reference/python-advanced.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/docs/reference/python-api.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/docs/reference/storage.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/docs/trust.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/docs/tutorial.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/docs/workflows/check-files.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/docs/workflows/lake-projects.md +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/examples/mathlib.toml +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/__init__.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/__main__.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/_git.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/_paths.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/backends.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/bundles.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/capsules.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/comparison.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/console.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/context_resolution.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/decisions.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/declaration_index.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/declaration_index_build.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/declaration_index_oci.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/diagnostics.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/__init__.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/analyzer.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/api.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/candidate.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/catalog.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/catalog_build.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/catalog_cli.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/catalog_manifest.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/data/catalog.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/defaults.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/errors.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/history.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/memo.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/module_inventory.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/planner.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/policy.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/probe.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/py.typed +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/result.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/schema_resources.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/schemas/catalog-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/schemas/plan-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/discovery/schemas/result-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/environments.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/errors.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/events.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/facade.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/frontmatter.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/header_cache.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/health.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/identifier_resolver.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/import_syntax.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/lake.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/lake_cache.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/lockfiles.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/locking.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/matrix.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/models.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/oci_protocol.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/packs.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/policies.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/profiling.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/programs.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/project_sharing.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/projects.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/publisher_verification.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/py.typed +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/references.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/resolver.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/schema_resources.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/serialization.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/shared_projects.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/specs.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/store.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/timings.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/toolchain_oci.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/toolchain_slim.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/toolchains.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/verification.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime/wire.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime.egg-info/dependency_links.txt +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime.egg-info/entry_points.txt +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime.egg-info/requires.txt +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/lean_runtime.egg-info/top_level.txt +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/mkdocs.yml +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/schemas/attestation-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/schemas/check-batch-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/schemas/cleanup-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/schemas/comparison-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/schemas/execution-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/schemas/inspect-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/schemas/matrix-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/schemas/plan-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/schemas/profile-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/schemas/publication-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/schemas/verify-v1.schema.json +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/scripts/announcement_gate.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/scripts/catalog_update.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/scripts/lake_cache_gate.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/scripts/prepare_declaration_index.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/scripts/publish_declaration_index.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/scripts/registry_preflight.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/scripts/run_compatibility.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/scripts/run_v1_case_study.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/scripts/smoke_wheel.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/setup.cfg +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/conftest.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/discovery/e2e/test_real_discovery.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/discovery/test_analyzer.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/discovery/test_catalog.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/discovery/test_catalog_build.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/discovery/test_discovery_schema_resources.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/discovery/test_memo.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/discovery/test_planner.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/discovery/test_policy.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_announcement_gate.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_backend.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_capsules.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_console.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_context_resolution.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_declaration_index.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_diagnostics.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_environment_integration.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_events_health.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_facade.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_frontmatter.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_git.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_header_cache.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_identifier_resolver.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_interactive.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_lake_cache.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_lockfiles.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_locking.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_models.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_multifile.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_oci_protocol.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_packs.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_paths.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_prebuilt_policy.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_programs.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_project_publish.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_projects.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_references.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_runtime.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_schema_resources.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_schemas.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_signatures.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_specs.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_store.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_toolchain_oci.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_toolchain_slim.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_toolchains.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_v1_precision.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_verification_inventory.py +0 -0
- {lean_runtime-4.22.0 → lean_runtime-4.23.0}/tests/test_verification_probe.py +0 -0
|
@@ -119,18 +119,20 @@ def _render_storage(status: StoreStatus) -> None:
|
|
|
119
119
|
("Executions", status.executions, status.executions_bytes),
|
|
120
120
|
("Scratch", status.scratch_workspaces, status.scratch_bytes),
|
|
121
121
|
)
|
|
122
|
+
label_width = max(len(label) for label, _count, _bytes in rows)
|
|
123
|
+
count_width = max(8, *(len(str(count)) for _label, count, _bytes in rows if count is not None))
|
|
122
124
|
for label, count, bytes_used in rows:
|
|
123
|
-
counted = f"{count:>
|
|
125
|
+
counted = f"{count:>{count_width}}" if count is not None else " " * count_width
|
|
124
126
|
size_column = style.cyan(f"{format_byte_size(bytes_used):>10}")
|
|
125
|
-
print(f" {label:<
|
|
126
|
-
total_label = style.bold(f"{'Materialized':<
|
|
127
|
+
print(f" {label:<{label_width}} {counted} {size_column}")
|
|
128
|
+
total_label = style.bold(f"{'Materialized':<{label_width}}")
|
|
127
129
|
total_size = style.bold(f"{format_byte_size(status.bytes_used):>10}")
|
|
128
130
|
free_note = style.dim(f"({format_byte_size(status.bytes_free)} free on disk)")
|
|
129
|
-
print(f" {total_label}
|
|
131
|
+
print(f" {total_label} {' ' * count_width} {total_size} {free_note}")
|
|
130
132
|
if status.allocated_bytes:
|
|
131
|
-
allocated_label = style.bold(f"{'Allocated*':<
|
|
133
|
+
allocated_label = style.bold(f"{'Allocated*':<{label_width}}")
|
|
132
134
|
allocated_size = style.bold(f"{format_byte_size(status.allocated_bytes):>10}")
|
|
133
|
-
print(f" {allocated_label}
|
|
135
|
+
print(f" {allocated_label} {' ' * count_width} {allocated_size}")
|
|
134
136
|
print(style.dim(" * hard links counted once; copy-on-write blocks may still be shared"))
|
|
135
137
|
if status.environment_usage:
|
|
136
138
|
print()
|
|
@@ -886,6 +888,13 @@ Advanced namespaces:
|
|
|
886
888
|
adopt.add_argument(
|
|
887
889
|
"--dry-run", action="store_true", help="show the adoption plan without changing anything"
|
|
888
890
|
)
|
|
891
|
+
adopt.add_argument(
|
|
892
|
+
"--no-agents",
|
|
893
|
+
dest="agents",
|
|
894
|
+
action="store_false",
|
|
895
|
+
default=True,
|
|
896
|
+
help="skip generating AGENTS.md",
|
|
897
|
+
)
|
|
889
898
|
_add_common_output(adopt)
|
|
890
899
|
adopt.set_defaults(command="attach", recursive=None, execute=False)
|
|
891
900
|
|
|
@@ -1150,6 +1159,13 @@ Advanced namespaces:
|
|
|
1150
1159
|
pshare.add_argument(
|
|
1151
1160
|
"--dry-run", action="store_true", help="show the sharing plan without changing anything"
|
|
1152
1161
|
)
|
|
1162
|
+
pshare.add_argument(
|
|
1163
|
+
"--no-agents",
|
|
1164
|
+
dest="agents",
|
|
1165
|
+
action="store_false",
|
|
1166
|
+
default=True,
|
|
1167
|
+
help="skip generating AGENTS.md",
|
|
1168
|
+
)
|
|
1153
1169
|
_add_common_output(pshare)
|
|
1154
1170
|
pshare.set_defaults(command="attach", recursive=None, execute=False)
|
|
1155
1171
|
punshare = pc.add_parser("unshare", help="materialize independent dependency copies")
|
|
@@ -1679,9 +1695,19 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1679
1695
|
if args.agents:
|
|
1680
1696
|
print(f"Agent guide: {init_result.root / 'AGENTS.md'}")
|
|
1681
1697
|
project_name = init_plan.project_name or init_result.root.name
|
|
1682
|
-
|
|
1683
|
-
|
|
1698
|
+
# Lake capitalizes the module directory during `lake init`, so derive
|
|
1699
|
+
# the hint from the file it actually created rather than the raw name.
|
|
1700
|
+
created = sorted(
|
|
1701
|
+
path
|
|
1702
|
+
for path in init_result.root.glob("*/Basic.lean")
|
|
1703
|
+
if not path.parent.name.startswith(".")
|
|
1684
1704
|
)
|
|
1705
|
+
module_file = (
|
|
1706
|
+
created[0].relative_to(init_result.root)
|
|
1707
|
+
if created
|
|
1708
|
+
else Path(project_name) / "Basic.lean"
|
|
1709
|
+
)
|
|
1710
|
+
print(f"Next: cd {init_result.root} && lean-runtime check {module_file}")
|
|
1685
1711
|
return 0
|
|
1686
1712
|
if args.command == "scan":
|
|
1687
1713
|
scan_result = runtime.scan_projects(args.path, recursive=args.recursive)
|
|
@@ -1793,8 +1819,24 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1793
1819
|
recursive=args.recursive,
|
|
1794
1820
|
plan=adoption_plan,
|
|
1795
1821
|
)
|
|
1822
|
+
# Adoption is the canonical moment a repository becomes agent-ready,
|
|
1823
|
+
# including re-runs against already-attached projects.
|
|
1824
|
+
agent_guides = (
|
|
1825
|
+
[
|
|
1826
|
+
guide
|
|
1827
|
+
for attached in adoption_result.results
|
|
1828
|
+
if (guide := runtime.write_agents_guide(attached.root)) is not None
|
|
1829
|
+
]
|
|
1830
|
+
if args.agents
|
|
1831
|
+
else []
|
|
1832
|
+
)
|
|
1796
1833
|
if args.json:
|
|
1797
|
-
_json(
|
|
1834
|
+
_json(
|
|
1835
|
+
{
|
|
1836
|
+
**adoption_result.to_dict(),
|
|
1837
|
+
"agent_guides": [str(guide) for guide in agent_guides],
|
|
1838
|
+
}
|
|
1839
|
+
)
|
|
1798
1840
|
else:
|
|
1799
1841
|
_render_adoption_plan(adoption_result.plan)
|
|
1800
1842
|
for attached in adoption_result.results:
|
|
@@ -1802,6 +1844,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1802
1844
|
f"{attached.action}: {attached.root} · "
|
|
1803
1845
|
f"{format_byte_size(attached.reclaimed_bytes)} replaced"
|
|
1804
1846
|
)
|
|
1847
|
+
for guide in agent_guides:
|
|
1848
|
+
print(f"Agent guide: {guide}")
|
|
1805
1849
|
for root, message in adoption_result.failures:
|
|
1806
1850
|
print(f"failed: {root}: {message}", file=sys.stderr)
|
|
1807
1851
|
return 0 if adoption_result.ok else 1
|
|
@@ -1958,6 +2002,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1958
2002
|
name=args.name,
|
|
1959
2003
|
build_timeout=args.timeout,
|
|
1960
2004
|
accelerate=args.accelerate,
|
|
2005
|
+
# Acquire means the whole environment: demanding the root
|
|
2006
|
+
# module keeps a sparse capsule from satisfying it with an
|
|
2007
|
+
# empty closure that only looks ready.
|
|
2008
|
+
import_roots=(ROOT_MODULE,),
|
|
1961
2009
|
)
|
|
1962
2010
|
_json(environment.inspect().to_dict())
|
|
1963
2011
|
if args.timings:
|
|
@@ -2252,12 +2300,22 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2252
2300
|
"toolchain": project_status.toolchain,
|
|
2253
2301
|
"manifest": str(project_status.manifest) if project_status.manifest else None,
|
|
2254
2302
|
"attached": (project_status.root / "lean-runtime.toml").is_file(),
|
|
2303
|
+
"agents_guide": (
|
|
2304
|
+
str(project_status.root / "AGENTS.md")
|
|
2305
|
+
if (project_status.root / "AGENTS.md").is_file()
|
|
2306
|
+
else None
|
|
2307
|
+
),
|
|
2255
2308
|
}
|
|
2256
2309
|
if args.json:
|
|
2257
2310
|
_json(status_payload)
|
|
2258
2311
|
else:
|
|
2259
|
-
|
|
2260
|
-
|
|
2312
|
+
display_payload = dict(status_payload)
|
|
2313
|
+
if display_payload.get("kind") == "project" and (
|
|
2314
|
+
display_payload.get("agents_guide") is None
|
|
2315
|
+
):
|
|
2316
|
+
display_payload["agents_guide"] = "missing (`lean-runtime adopt` writes one)"
|
|
2317
|
+
print(f"{display_payload['kind'].title()}")
|
|
2318
|
+
for key, value in display_payload.items():
|
|
2261
2319
|
if key == "kind":
|
|
2262
2320
|
continue
|
|
2263
2321
|
label = key.replace("_", " ").title()
|
|
@@ -2547,7 +2605,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2547
2605
|
watched = Path(args.inputs[0]).expanduser().resolve()
|
|
2548
2606
|
if not watched.is_file():
|
|
2549
2607
|
raise ValueError(f"watched Lean file does not exist: {watched}")
|
|
2550
|
-
print(f"Watching {watched} · Ctrl-C to stop")
|
|
2608
|
+
print(f"Watching {watched} · Ctrl-C to stop", flush=True)
|
|
2551
2609
|
if os.environ.get(_HEADER_SNAPSHOTS_VARIABLE) is None:
|
|
2552
2610
|
runtime.header_cache.enabled = True
|
|
2553
2611
|
previous: tuple[int, int] | None = None
|
|
@@ -2563,6 +2621,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2563
2621
|
policy=_policy(args),
|
|
2564
2622
|
)
|
|
2565
2623
|
_emit_result(watched_result, False)
|
|
2624
|
+
sys.stdout.flush()
|
|
2566
2625
|
time.sleep(args.watch_interval)
|
|
2567
2626
|
check_subject = (
|
|
2568
2627
|
Path(args.inputs[-1]).name
|
|
@@ -2614,10 +2673,16 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2614
2673
|
if args.include:
|
|
2615
2674
|
raise ValueError("local project checks do not accept --include")
|
|
2616
2675
|
display_path = "<stdin>"
|
|
2676
|
+
stdin_project = args.project
|
|
2677
|
+
if stdin_project is None and args.toolchain is None:
|
|
2678
|
+
try:
|
|
2679
|
+
stdin_project = discover_project(Path.cwd()).root
|
|
2680
|
+
except ProjectNotFoundError:
|
|
2681
|
+
stdin_project = None
|
|
2617
2682
|
result = runtime.check(
|
|
2618
2683
|
sys.stdin.read(),
|
|
2619
2684
|
toolchain=args.toolchain,
|
|
2620
|
-
project=
|
|
2685
|
+
project=stdin_project,
|
|
2621
2686
|
policy=_policy(args),
|
|
2622
2687
|
)
|
|
2623
2688
|
source_file = None
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import re
|
|
5
6
|
import threading
|
|
6
7
|
import time
|
|
7
8
|
|
|
8
9
|
from ..errors import LeanRuntimeError
|
|
9
|
-
from
|
|
10
|
+
from ..models import ExecutionResult
|
|
11
|
+
from .candidate import Candidate, DiscoveryPlan
|
|
12
|
+
from .catalog import CatalogEntry
|
|
10
13
|
from .probe import CandidateProbe, ProbeIntegrityFailure, ProbeUnavailable
|
|
11
14
|
from .result import (
|
|
12
15
|
AttemptStatus,
|
|
@@ -27,6 +30,8 @@ DISCOVERY_EXHAUSTED = "DISCOVERY_EXHAUSTED"
|
|
|
27
30
|
DISCOVERY_EXPLICIT_LOCK = "DISCOVERY_EXPLICIT_LOCK"
|
|
28
31
|
DISCOVERY_CANCELLED = "DISCOVERY_CANCELLED"
|
|
29
32
|
DISCOVERY_RUNTIME_ERROR = "DISCOVERY_RUNTIME_ERROR"
|
|
33
|
+
DISCOVERY_MODULE_UNAVAILABLE = "DISCOVERY_MODULE_UNAVAILABLE"
|
|
34
|
+
DISCOVERY_VERDICT_BOUNDED = "DISCOVERY_VERDICT_BOUNDED"
|
|
30
35
|
CANDIDATE_LEAN_REJECTED = "CANDIDATE_LEAN_REJECTED"
|
|
31
36
|
CANDIDATE_UNAVAILABLE = "CANDIDATE_UNAVAILABLE"
|
|
32
37
|
CANDIDATE_INTEGRITY_FAILURE = "CANDIDATE_INTEGRITY_FAILURE"
|
|
@@ -145,6 +150,63 @@ def _rejection_detail(stderr: str, stdout: str) -> str:
|
|
|
145
150
|
return stderr.strip() or stdout.strip() or "Lean rejected the source"
|
|
146
151
|
|
|
147
152
|
|
|
153
|
+
# Lean reports a missing import as `unknown module prefix 'X'` (directory-shaped
|
|
154
|
+
# roots) or `unknown module 'X'`; both are decidable against candidate
|
|
155
|
+
# inventories without running another compiler.
|
|
156
|
+
_UNKNOWN_MODULE = re.compile(r"unknown module(?: prefix)? '([^']+)'")
|
|
157
|
+
# Identifier-level failures are the rejection class where an older environment
|
|
158
|
+
# legitimately changes the verdict (renamed or deleted lemmas), so they always
|
|
159
|
+
# justify continuing the candidate march.
|
|
160
|
+
_IDENTIFIER_MARKERS = ("unknown identifier", "unknown constant")
|
|
161
|
+
# Positive evidence that the source itself is the problem. Kept deliberately
|
|
162
|
+
# narrow: anything unrecognized preserves the ordinary march.
|
|
163
|
+
_PROOF_LEVEL_MARKERS = ("type mismatch", "unsolved goals", "unexpected token")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _provides_module(entry: CatalogEntry, name: str) -> bool:
|
|
167
|
+
return name in entry.modules or any(module.startswith(name + ".") for module in entry.modules)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _rejection_verdict(
|
|
171
|
+
result: ExecutionResult, candidates: tuple[Candidate, ...]
|
|
172
|
+
) -> tuple[str, tuple[str, ...]]:
|
|
173
|
+
"""Classify whether another candidate environment could change a rejection.
|
|
174
|
+
|
|
175
|
+
Returns one of:
|
|
176
|
+
- ``("module_unavailable", names)``: the source imports module roots that no
|
|
177
|
+
planned candidate provides; every further attempt must fail identically.
|
|
178
|
+
- ``("source_verdict", ())``: a proof-level failure (type mismatch,
|
|
179
|
+
unsolved goals, syntax); a second opinion never justifies a fresh
|
|
180
|
+
download — only local environments or an offline plan's source builds.
|
|
181
|
+
- ``("march", ())``: everything else — identifier-level failures, modules
|
|
182
|
+
another candidate does provide, and unclassifiable output all keep the
|
|
183
|
+
ordinary bounded march.
|
|
184
|
+
"""
|
|
185
|
+
text = "\n".join(
|
|
186
|
+
(
|
|
187
|
+
*(item.message for item in result.diagnostics),
|
|
188
|
+
result.stderr,
|
|
189
|
+
result.stdout,
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
missing = tuple(dict.fromkeys(_UNKNOWN_MODULE.findall(text)))
|
|
193
|
+
if missing:
|
|
194
|
+
unavailable = tuple(
|
|
195
|
+
name
|
|
196
|
+
for name in missing
|
|
197
|
+
if not any(_provides_module(candidate.entry, name) for candidate in candidates)
|
|
198
|
+
)
|
|
199
|
+
if unavailable:
|
|
200
|
+
return "module_unavailable", unavailable
|
|
201
|
+
return "march", ()
|
|
202
|
+
lowered = text.lower()
|
|
203
|
+
if any(marker in lowered for marker in _IDENTIFIER_MARKERS):
|
|
204
|
+
return "march", ()
|
|
205
|
+
if any(marker in lowered for marker in _PROOF_LEVEL_MARKERS):
|
|
206
|
+
return "source_verdict", ()
|
|
207
|
+
return "march", ()
|
|
208
|
+
|
|
209
|
+
|
|
148
210
|
def discover(
|
|
149
211
|
source: str,
|
|
150
212
|
plan: DiscoveryPlan,
|
|
@@ -189,6 +251,11 @@ def discover(
|
|
|
189
251
|
raise ValueError("an authoritative candidate probe is required")
|
|
190
252
|
attempts: list[CandidateAttempt] = []
|
|
191
253
|
remote_acquisitions = 0
|
|
254
|
+
# Set once a proof-level rejection shows the source itself is the problem:
|
|
255
|
+
# remaining candidates are only worth a look when reaching them costs no
|
|
256
|
+
# download (already local, or an offline plan's deliberate source builds).
|
|
257
|
+
local_candidates_only = False
|
|
258
|
+
verdict_bounded_skips = 0
|
|
192
259
|
for candidate in plan.planned_candidates:
|
|
193
260
|
if cancel is not None and cancel.is_set():
|
|
194
261
|
return DiscoveryResult(
|
|
@@ -217,6 +284,12 @@ def discover(
|
|
|
217
284
|
candidate_is_local = any(
|
|
218
285
|
reason.code == "RANK_LOCAL_AVAILABLE" for reason in candidate.reasons
|
|
219
286
|
)
|
|
287
|
+
# A proof-level rejection never justifies a *download*. Offline plans
|
|
288
|
+
# (allow_download=False) acquire candidates by deliberate local source
|
|
289
|
+
# builds, so their march is preserved in full.
|
|
290
|
+
if local_candidates_only and not candidate_is_local and plan.policy.allow_download:
|
|
291
|
+
verdict_bounded_skips += 1
|
|
292
|
+
continue
|
|
220
293
|
if not candidate_is_local and remote_acquisitions >= plan.policy.max_remote_acquisitions:
|
|
221
294
|
return DiscoveryResult(
|
|
222
295
|
status="not_found",
|
|
@@ -427,6 +500,29 @@ def discover(
|
|
|
427
500
|
duration_seconds=time.monotonic() - started,
|
|
428
501
|
diagnostics=attempt.diagnostics,
|
|
429
502
|
)
|
|
503
|
+
if outcome_status == "lean_rejected":
|
|
504
|
+
verdict, unavailable = _rejection_verdict(result, plan.planned_candidates)
|
|
505
|
+
if verdict == "module_unavailable":
|
|
506
|
+
# Statically decidable: no candidate can change this verdict.
|
|
507
|
+
return DiscoveryResult(
|
|
508
|
+
status="not_found",
|
|
509
|
+
confidence="exhausted",
|
|
510
|
+
outcome="source_rejected",
|
|
511
|
+
completion="complete",
|
|
512
|
+
plan=plan,
|
|
513
|
+
attempts=tuple(attempts),
|
|
514
|
+
duration_seconds=time.monotonic() - started,
|
|
515
|
+
diagnostics=(
|
|
516
|
+
ResultDiagnostic(
|
|
517
|
+
DISCOVERY_MODULE_UNAVAILABLE,
|
|
518
|
+
"no cataloged environment provides module(s): "
|
|
519
|
+
+ ", ".join(unavailable),
|
|
520
|
+
),
|
|
521
|
+
*attempt.diagnostics,
|
|
522
|
+
),
|
|
523
|
+
)
|
|
524
|
+
if verdict == "source_verdict":
|
|
525
|
+
local_candidates_only = True
|
|
430
526
|
|
|
431
527
|
if time.monotonic() >= wall_deadline or probe_remaining <= 0:
|
|
432
528
|
reason = ResultDiagnostic(DISCOVERY_TIME_LIMIT, "total wall-clock budget expired")
|
|
@@ -465,6 +561,17 @@ def discover(
|
|
|
465
561
|
if attempt.status == "timeout"
|
|
466
562
|
for diagnostic in attempt.diagnostics
|
|
467
563
|
)
|
|
564
|
+
skip_diagnostics = (
|
|
565
|
+
(
|
|
566
|
+
ResultDiagnostic(
|
|
567
|
+
DISCOVERY_VERDICT_BOUNDED,
|
|
568
|
+
f"skipped {verdict_bounded_skips} non-local candidate(s): a proof-level "
|
|
569
|
+
"rejection does not justify downloading further environments",
|
|
570
|
+
),
|
|
571
|
+
)
|
|
572
|
+
if verdict_bounded_skips
|
|
573
|
+
else ()
|
|
574
|
+
)
|
|
468
575
|
return DiscoveryResult(
|
|
469
576
|
status="not_found",
|
|
470
577
|
confidence=confidence,
|
|
@@ -473,5 +580,5 @@ def discover(
|
|
|
473
580
|
plan=plan,
|
|
474
581
|
attempts=tuple(attempts),
|
|
475
582
|
duration_seconds=time.monotonic() - started,
|
|
476
|
-
diagnostics=(*timeout_diagnostics, reason, *rejection_diagnostics),
|
|
583
|
+
diagnostics=(*timeout_diagnostics, reason, *skip_diagnostics, *rejection_diagnostics),
|
|
477
584
|
)
|
|
@@ -887,6 +887,17 @@ class OCIEnvironmentCache:
|
|
|
887
887
|
capsule = CapsuleManifest.from_dict(config["capsule"])
|
|
888
888
|
if capsule.lock_id != lock.lock_id or capsule.toolchain != lock.toolchain:
|
|
889
889
|
raise EnvironmentError("downloadable capsule does not match its lock")
|
|
890
|
+
# A capsule that lacks a requested root cannot satisfy this acquisition;
|
|
891
|
+
# silently projecting a partial closure would yield an environment that
|
|
892
|
+
# claims readiness while missing the modules the caller asked for.
|
|
893
|
+
available = {module.name for module in capsule.modules}
|
|
894
|
+
unavailable = tuple(root for root in roots if root not in available)
|
|
895
|
+
if unavailable:
|
|
896
|
+
raise DownloadUnavailable(
|
|
897
|
+
"downloadable capsule does not provide module(s): " + ", ".join(unavailable),
|
|
898
|
+
reason_code="capsule_roots_unavailable",
|
|
899
|
+
retryable=False,
|
|
900
|
+
)
|
|
890
901
|
closure = capsule.closure(roots)
|
|
891
902
|
module_names = tuple(module.name for module in closure)
|
|
892
903
|
descriptors = {
|
|
@@ -9,6 +9,7 @@ its artifact cache without changing the public ``Runtime`` API.
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import json
|
|
12
13
|
import os
|
|
13
14
|
import re
|
|
14
15
|
import subprocess
|
|
@@ -17,6 +18,7 @@ import tempfile
|
|
|
17
18
|
import threading
|
|
18
19
|
import time
|
|
19
20
|
from collections.abc import Callable, Sequence
|
|
21
|
+
from contextlib import AbstractContextManager, nullcontext
|
|
20
22
|
from dataclasses import replace
|
|
21
23
|
from pathlib import Path
|
|
22
24
|
from typing import TYPE_CHECKING
|
|
@@ -28,6 +30,7 @@ else: # pragma: no cover - exercised by the Python 3.10 CI job
|
|
|
28
30
|
|
|
29
31
|
from .errors import ProjectError
|
|
30
32
|
from .import_syntax import IMPORT_STATEMENT
|
|
33
|
+
from .locking import FileLock
|
|
31
34
|
from .models import ExecutionResult, PhaseTiming
|
|
32
35
|
from .policies import ExecutionPolicy
|
|
33
36
|
from .project_sharing import project_sharing_enabled
|
|
@@ -91,6 +94,63 @@ class ProjectExecutor:
|
|
|
91
94
|
def __init__(self, runtime: Runtime) -> None:
|
|
92
95
|
self.runtime = runtime
|
|
93
96
|
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _declares_dependencies(context: ProjectContext) -> bool:
|
|
99
|
+
"""Whether the lakefile requires packages Lake would have to fetch."""
|
|
100
|
+
try:
|
|
101
|
+
text = context.lakefile.read_text(encoding="utf-8")
|
|
102
|
+
except OSError:
|
|
103
|
+
return False
|
|
104
|
+
if context.lakefile.name == "lakefile.toml":
|
|
105
|
+
try:
|
|
106
|
+
return bool(tomllib.loads(text).get("require"))
|
|
107
|
+
except tomllib.TOMLDecodeError:
|
|
108
|
+
return False
|
|
109
|
+
return any(line.lstrip().startswith("require ") for line in text.splitlines())
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def _packages_materialized(root: Path, manifest: Path) -> bool:
|
|
113
|
+
"""Whether every manifest package already has a checkout on disk."""
|
|
114
|
+
try:
|
|
115
|
+
document = json.loads(manifest.read_text(encoding="utf-8"))
|
|
116
|
+
except (OSError, ValueError):
|
|
117
|
+
return True # let Lake report the manifest problem itself
|
|
118
|
+
packages = document.get("packages")
|
|
119
|
+
if not isinstance(packages, list):
|
|
120
|
+
return True
|
|
121
|
+
packages_dir = root / str(document.get("packagesDir", ".lake/packages"))
|
|
122
|
+
for package in packages:
|
|
123
|
+
name = package.get("name") if isinstance(package, dict) else None
|
|
124
|
+
if isinstance(name, str) and name and not (packages_dir / name).exists():
|
|
125
|
+
return False
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
def _bootstrap_guard(self, context: ProjectContext) -> AbstractContextManager[object]:
|
|
129
|
+
"""Never let a check implicitly resolve dependencies, and never in parallel.
|
|
130
|
+
|
|
131
|
+
A missing manifest with declared dependencies means ``lake env`` would
|
|
132
|
+
run an implicit ``lake update`` — cloning and compiling dependencies
|
|
133
|
+
from source, which is Lake's job to do once, deliberately. Concurrent
|
|
134
|
+
checks against a manifest whose packages are not yet materialized race
|
|
135
|
+
Lake's checkout of the same directories, so exactly one check may run
|
|
136
|
+
until the packages exist.
|
|
137
|
+
"""
|
|
138
|
+
manifest = context.current_manifest()
|
|
139
|
+
if manifest is None:
|
|
140
|
+
if self._declares_dependencies(context):
|
|
141
|
+
raise ProjectError(
|
|
142
|
+
f"{context.root} declares dependencies but has no lake-manifest.json; "
|
|
143
|
+
"run `lake update` once to resolve versions, then `lean-runtime build` "
|
|
144
|
+
"to restore shared dependency artifacts"
|
|
145
|
+
)
|
|
146
|
+
elif self._packages_materialized(context.root, manifest):
|
|
147
|
+
return nullcontext()
|
|
148
|
+
return FileLock(
|
|
149
|
+
context.root / ".lake" / "lean-runtime" / "bootstrap.lock",
|
|
150
|
+
timeout=3600,
|
|
151
|
+
owner={"operation": "project_bootstrap", "root": str(context.root)},
|
|
152
|
+
)
|
|
153
|
+
|
|
94
154
|
def _checked_with_header_snapshots(
|
|
95
155
|
self,
|
|
96
156
|
context: ProjectContext,
|
|
@@ -162,7 +222,8 @@ class ProjectExecutor:
|
|
|
162
222
|
context, provenance.workspace_digest, relative, text, command, execute, cancel
|
|
163
223
|
)
|
|
164
224
|
|
|
165
|
-
|
|
225
|
+
with self._bootstrap_guard(context):
|
|
226
|
+
result = self._build_missing_local_import(context, text, run(), run, policy, cancel)
|
|
166
227
|
return self._with_identifier_hints(context, result)
|
|
167
228
|
|
|
168
229
|
def check_source(
|
|
@@ -213,7 +274,10 @@ class ProjectExecutor:
|
|
|
213
274
|
cancel,
|
|
214
275
|
)
|
|
215
276
|
|
|
216
|
-
|
|
277
|
+
with self._bootstrap_guard(context):
|
|
278
|
+
result = self._build_missing_local_import(
|
|
279
|
+
context, source, run(), run, policy, cancel
|
|
280
|
+
)
|
|
217
281
|
return self._with_identifier_hints(context, result)
|
|
218
282
|
|
|
219
283
|
def _build_missing_local_import(
|
|
@@ -199,7 +199,12 @@ def _discovery_failure(result: DiscoveryResult) -> str:
|
|
|
199
199
|
return (details[0] if details else "no compatible environment was found") + suffix
|
|
200
200
|
|
|
201
201
|
|
|
202
|
-
def _discovery_summary(result: DiscoveryResult) -> str:
|
|
202
|
+
def _discovery_summary(result: DiscoveryResult) -> str | None:
|
|
203
|
+
# A finished search that ends in a compiler rejection needs no epilogue:
|
|
204
|
+
# an environment was found and the verdict is the diagnostic above. Only
|
|
205
|
+
# an *incomplete* search (a limit stopped it) is worth a trailing line.
|
|
206
|
+
if result.outcome == "source_rejected" and result.completion == "complete":
|
|
207
|
+
return None
|
|
203
208
|
tested = len(result.attempts)
|
|
204
209
|
planned = len(getattr(result.plan, "planned_candidates", result.plan.candidates))
|
|
205
210
|
untried = max(0, planned - tested)
|
|
@@ -592,10 +597,13 @@ def run(
|
|
|
592
597
|
)
|
|
593
598
|
if context_resolution.kind in {"lock", "toolchain", "project", "discovery"}:
|
|
594
599
|
result = replace(result, timings=(preparation, *result.timings))
|
|
595
|
-
|
|
600
|
+
discovery_summary = (
|
|
601
|
+
_discovery_summary(rejected_discovery) if rejected_discovery is not None else None
|
|
602
|
+
)
|
|
603
|
+
if discovery_summary is not None:
|
|
596
604
|
result = replace(
|
|
597
605
|
result,
|
|
598
|
-
hints=(*result.hints,
|
|
606
|
+
hints=(*result.hints, discovery_summary),
|
|
599
607
|
)
|
|
600
608
|
renderer.close()
|
|
601
609
|
_emit(
|
|
@@ -607,9 +615,7 @@ def run(
|
|
|
607
615
|
summary_elapsed_seconds=(
|
|
608
616
|
rejected_discovery.duration_seconds if rejected_discovery is not None else None
|
|
609
617
|
),
|
|
610
|
-
discovery_summary=
|
|
611
|
-
_discovery_summary(rejected_discovery) if rejected_discovery is not None else None
|
|
612
|
-
),
|
|
618
|
+
discovery_summary=discovery_summary,
|
|
613
619
|
)
|
|
614
620
|
if result.ok:
|
|
615
621
|
return 0
|
|
@@ -1605,6 +1605,19 @@ class Runtime:
|
|
|
1605
1605
|
failures.append((project.root, str(exc)))
|
|
1606
1606
|
return AdoptionBatchResult(selected_plan, tuple(results), tuple(failures))
|
|
1607
1607
|
|
|
1608
|
+
def write_agents_guide(self, root: str | os.PathLike[str]) -> Path | None:
|
|
1609
|
+
"""Write the default AGENTS.md into a project root.
|
|
1610
|
+
|
|
1611
|
+
Returns the created path, or ``None`` when a guide already exists —
|
|
1612
|
+
an existing guide is the project's own voice and is never replaced.
|
|
1613
|
+
"""
|
|
1614
|
+
target = Path(root).expanduser().resolve()
|
|
1615
|
+
guide = target / "AGENTS.md"
|
|
1616
|
+
if guide.exists():
|
|
1617
|
+
return None
|
|
1618
|
+
guide.write_text(_DEFAULT_AGENTS_GUIDE, encoding="utf-8")
|
|
1619
|
+
return guide
|
|
1620
|
+
|
|
1608
1621
|
def scan_projects(
|
|
1609
1622
|
self, path: str | os.PathLike[str], *, recursive: bool = True
|
|
1610
1623
|
) -> ProjectScanResult:
|