gitgrip 2.0.0a4__tar.gz → 2.0.0a6__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.
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/PKG-INFO +14 -10
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/README.md +12 -9
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gitgrip.egg-info/PKG-INFO +14 -10
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gitgrip.egg-info/SOURCES.txt +46 -1
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gitgrip.egg-info/requires.txt +1 -0
- gitgrip-2.0.0a6/gr2/api_core.py +306 -0
- gitgrip-2.0.0a6/gr2/schemas/gr2-workspace-spec-v1.schema.json +10 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2_overlay/__init__.py +8 -3
- gitgrip-2.0.0a6/prototypes/concurrent_event_stress.py +364 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/lane_workspace_prototype.py +3 -3
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/python_hook_runtime_playground.py +6 -6
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/real_git_playground.py +2 -2
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/repo_maintenance_prototype.py +1 -1
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/pyproject.toml +14 -3
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/app.py +769 -92
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/channel_bridge.py +28 -3
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/clone_exec.py +57 -6
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/commit.py +42 -11
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/consent.py +33 -6
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/events.py +45 -14
- gitgrip-2.0.0a6/python_cli/exit_codes.py +38 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/file_exec.py +5 -5
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/git_review.py +44 -1
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/gitinclude.py +15 -3
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/gitops.py +150 -4
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/grip.py +127 -4
- gitgrip-2.0.0a6/python_cli/grip_cli.py +1975 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/hooks.py +94 -6
- gitgrip-2.0.0a6/python_cli/layout.py +102 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/migration.py +123 -12
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/open_gr_review.py +28 -11
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/platform.py +218 -13
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/pr.py +293 -8
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/project_review.py +7 -1
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/push.py +8 -6
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/review.py +58 -21
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/review_dispatch.py +4 -5
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/review_rebind.py +2 -2
- gitgrip-2.0.0a6/python_cli/review_records.py +143 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/review_run.py +343 -32
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/review_runners.py +35 -8
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/spec_apply.py +479 -42
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/syncops.py +51 -10
- gitgrip-2.0.0a6/python_cli/version.py +373 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/workspace_snapshot.py +1 -1
- gitgrip-2.0.0a6/tests/test_api_core_seam.py +360 -0
- gitgrip-2.0.0a6/tests/test_api_dump.py +775 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_apply_convergence.py +33 -15
- gitgrip-2.0.0a6/tests/test_apply_gr1_sibling_adoption.py +358 -0
- gitgrip-2.0.0a6/tests/test_break_17_unit_path_containment.py +188 -0
- gitgrip-2.0.0a6/tests/test_break_18_gr1_sibling_units.py +482 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_cli_shape_witnesses.py +1 -1
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_clone_materialization.py +23 -19
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_conftest_git_isolation.py +1 -0
- gitgrip-2.0.0a6/tests/test_deprecation_registry.py +277 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_ensure_lane_checkout_reference_clone.py +1 -1
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_event_log_integrity.py +152 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_events.py +14 -10
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_events_torn_line.py +5 -2
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_exec_apostrophe_and_oserror.py +2 -2
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_exec_single_string_and_materialize_plan.py +3 -3
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_git_review.py +201 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_gitinclude_compiler.py +50 -3
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_gr1_first_run_guidance.py +19 -5
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_gr2_review_receipt.py +2 -2
- gitgrip-2.0.0a6/tests/test_grip_cli.py +450 -0
- gitgrip-2.0.0a6/tests/test_grip_toml_schema_v1.py +33 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_hooks_consent.py +276 -0
- gitgrip-2.0.0a6/tests/test_hooks_group_names.py +63 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_lane_create_fork_base_cli.py +36 -3
- gitgrip-2.0.0a6/tests/test_lane_create_refusal_leaves_nothing.py +354 -0
- gitgrip-2.0.0a6/tests/test_lane_enter_hook_warnings.py +205 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_lane_lifecycle_safety.py +4 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_lane_repo_paths_and_all_skipped.py +4 -4
- gitgrip-2.0.0a6/tests/test_lane_show_name.py +46 -0
- gitgrip-2.0.0a6/tests/test_layout_census.py +180 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_materialization_plan_contract.py +16 -16
- gitgrip-2.0.0a6/tests/test_materialize_pin.py +413 -0
- gitgrip-2.0.0a6/tests/test_member_working_root_checkout_materialize.py +329 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_migration.py +247 -2
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_migration_unadopted_notice.py +10 -8
- gitgrip-2.0.0a6/tests/test_native_store_review_record_probes.py +266 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_open_gr_enter.py +22 -4
- gitgrip-2.0.0a6/tests/test_outbox_ack_after_effect.py +262 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_pr_events.py +8 -0
- gitgrip-2.0.0a6/tests/test_pr_group_legibility.py +236 -0
- gitgrip-2.0.0a6/tests/test_pr_merge_head_pin.py +393 -0
- gitgrip-2.0.0a6/tests/test_pr_merge_missing_target.py +694 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_pr_merge_partial_failure.py +9 -1
- gitgrip-2.0.0a6/tests/test_pr_view.py +502 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_project_file_materialization.py +4 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_project_review_three_repo.py +3 -3
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_propagation_state_machine.py +10 -22
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_readme_verbs_exist.py +67 -2
- gitgrip-2.0.0a6/tests/test_repo_path_read_through.py +382 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_bind_from_range.py +85 -1
- gitgrip-2.0.0a6/tests/test_review_bind_native_store.py +293 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_cli.py +4 -4
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_close_gr.py +49 -4
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_ephemeral.py +1 -1
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_open_lane.py +100 -9
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_rebind.py +22 -1
- gitgrip-2.0.0a6/tests/test_review_receipt_cleanup.py +92 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_run.py +7 -7
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_run_import_isolation.py +8 -1
- gitgrip-2.0.0a6/tests/test_review_run_integrity_recheck.py +361 -0
- gitgrip-2.0.0a6/tests/test_review_run_multi_repo.py +344 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_run_rerun_and_refusal_receipt.py +4 -4
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_runners.py +57 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_verify_inside.py +1 -1
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_rmtree_or_refuse.py +14 -3
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_single_repo_cwd_refusal.py +2 -2
- gitgrip-2.0.0a6/tests/test_snapshot_id_prefix_resolution.py +221 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_spec_apply_event_outcomes.py +5 -2
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_sprint21_sync_platform.py +228 -0
- gitgrip-2.0.0a6/tests/test_store_break_attempts.py +1958 -0
- gitgrip-2.0.0a6/tests/test_store_checkout_atomicity.py +405 -0
- gitgrip-2.0.0a6/tests/test_store_commit_noop.py +137 -0
- gitgrip-2.0.0a6/tests/test_store_declared_members.py +293 -0
- gitgrip-2.0.0a6/tests/test_store_git_native_smoke.py +220 -0
- gitgrip-2.0.0a6/tests/test_store_init_branch_inference.py +260 -0
- gitgrip-2.0.0a6/tests/test_store_log_adoption_boundary.py +205 -0
- gitgrip-2.0.0a6/tests/test_store_member_path_hygiene.py +128 -0
- gitgrip-2.0.0a6/tests/test_store_member_state.py +213 -0
- gitgrip-2.0.0a6/tests/test_store_migrate_branch_inference.py +106 -0
- gitgrip-2.0.0a6/tests/test_store_status_values.py +184 -0
- gitgrip-2.0.0a6/tests/test_store_verb_rename.py +140 -0
- gitgrip-2.0.0a6/tests/test_store_verb_witnesses.py +770 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_submodule_member_predicate.py +1 -1
- gitgrip-2.0.0a6/tests/test_superproject_import.py +384 -0
- gitgrip-2.0.0a6/tests/test_validate_repo_path_conflict.py +169 -0
- gitgrip-2.0.0a6/tests/test_version_identity.py +937 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_version_static.py +31 -8
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_workspace_convert_clone.py +1 -1
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_workspace_identity_boundary.py +11 -1
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_workspace_init_local_repos.py +3 -3
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_workspace_snapshot.py +1 -1
- gitgrip-2.0.0a4/prototypes/concurrent_event_stress.py +0 -199
- gitgrip-2.0.0a4/python_cli/grip_cli.py +0 -497
- gitgrip-2.0.0a4/tests/test_grip_cli.py +0 -536
- gitgrip-2.0.0a4/tests/test_grip_object_model.py +0 -357
- gitgrip-2.0.0a4/tests/test_snapshot_id_prefix_resolution.py +0 -132
- gitgrip-2.0.0a4/tests/test_store_verb_rename.py +0 -46
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gitgrip.egg-info/dependency_links.txt +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gitgrip.egg-info/entry_points.txt +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gitgrip.egg-info/top_level.txt +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/__init__.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/__init__.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/activate.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/agent_manifest.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/cli.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/cross_repo.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/drivers.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/introspection.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/language_drivers.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/objects.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/perf.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/refs.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/trust.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/types.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/units.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/overlay/workspace_spec.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/gr2/schemas/gr2-materialization-plan-v1.schema.json +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/__init__.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/cache_materialization_probe.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/concurrent_lease_stress.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/concurrent_workspace_cap_stress.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/contribution_protocol.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/cross_mode_lane_stress.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/jsonl_store.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/layout_model_probe.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/propagation_daemon.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/propagation_state_machine.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/python_exec_playground.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/python_migration_playground.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/python_review_checkout_playground.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/python_spec_apply_playground.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/real_git_lane_materialization.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/recall_lane_history.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/prototypes/repo_transport_probe.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/__init__.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/__main__.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/add.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/branch.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/config.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/env_exec.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/execops.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/failures.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/launch_exec.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/merge_verification.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/prune.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/review_ephemeral.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/staging_cleanup.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/target.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/python_cli/workspace_guidance.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/setup.cfg +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_add.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_agent_manifest_overlay_source.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_app_event_outcomes.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_branch.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_channel_bridge.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_cli_adapter_error_boundary.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_commit.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_config_base_path_guard.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_config_overlay.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_contribution_protocol.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_env_materialization.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_event_outcome_policy.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_execops.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_failure_event_outcome.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_first_five_minutes.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_foreground_launch_runtime.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_gr2_packaging.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_grip_hardening.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_grip_snapshot.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_hook_events.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_hook_hardening.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_jsonl_store.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_lane_create_refuses_own_branch.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_lane_lease_show_json.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_lane_toml_serialization.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_launch_primitive.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_m2_overlay_hindsight.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_m2_overlay_mem0.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_m2_overlay_zep.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_merge_evidence.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_merge_parent_verification.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_migration_overlay_compatibility.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_no_bare_python_cli_imports.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_null_current_lane_refusal.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_open_project_refusal_reason_cli.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_acceptance.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_activate.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_cli.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_cli_wiring.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_composition_merge.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_cross_repo_atomic_apply.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_cross_repo_perf_gate.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_cross_repo_rollback.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_driver_registry.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_introspection.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_multi_stack.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_object_encoding.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_perf_gates.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_python_driver.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_tier_b_encoding.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_tier_b_perf_gates.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_tier_b_repo_state.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_tier_b_staging_discipline.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_trust_model.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_overlay_workspace_spec.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_pr_merge_method.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_project_review_range_reconstruction.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_projection_tracked_dest.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_propagation_contribution.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_propagation_daemon.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_prune.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_push.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_repo_status_linked_worktree.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_repo_status_schema_version.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_repo_status_symlinked_git_dir.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_bind_fork_base.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_bind_verify.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_create_project_cli.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_review_dispatch.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_spec_declaration_ref_pin.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_staging_cleanup.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_sync_event_outcomes.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_sync_fast_forward.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_sync_fetch.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_sync_seed_local.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_sync_ssh_rewrite.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_sync_status_probe.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_target.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_tmux_launch_runtime.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_unit_abort.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_unit_apply.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_unit_apply_dry_run.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_unit_manifest_schema.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_unit_propose.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_unit_topological_apply.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_workspace_edit_lease_cap.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_workspace_init_from_topology.py +0 -0
- {gitgrip-2.0.0a4 → gitgrip-2.0.0a6}/tests/test_workspace_root_defaults.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitgrip
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0a6
|
|
4
4
|
Summary: Workspace layer for multi-repo work: workspaces, isolated lanes, and one grouped review per slice
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -12,6 +12,7 @@ Requires-Dist: jsonschema>=4.18
|
|
|
12
12
|
Provides-Extra: dev
|
|
13
13
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
14
14
|
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
15
|
+
Requires-Dist: packaging>=22; extra == "dev"
|
|
15
16
|
|
|
16
17
|
# gr2
|
|
17
18
|
|
|
@@ -76,7 +77,7 @@ gr2 commit -m "my change" --workspace-root ~/ws --owner-unit default
|
|
|
76
77
|
```bash
|
|
77
78
|
gr2 exec run ~/ws default --actor human:you -- pytest -q # one command, every lane repo
|
|
78
79
|
gr2 review create-project ~/ws default feat-x # prints gr:<sha>: one object to review
|
|
79
|
-
gr2 lane
|
|
80
|
+
gr2 lane show ~/ws default # where am I?
|
|
80
81
|
gr2 lane exit ~/ws default --actor human:you
|
|
81
82
|
```
|
|
82
83
|
|
|
@@ -86,7 +87,7 @@ gr2 lane exit ~/ws default --actor human:you
|
|
|
86
87
|
|
|
87
88
|
Alpha 2 is a working local workspace layer. Verified by running the walk above:
|
|
88
89
|
|
|
89
|
-
- `workspace` (init, materialize, status, migrate-gr1 from a gr1 gripspace), `lane` (create, enter, exit, current, lease), `store` (init,
|
|
90
|
+
- `workspace` (init, materialize, status, migrate-gr1 from a gr1 gripspace), `lane` (create, enter, exit, current, lease), `store` (init, commit, log, diff, checkout), `exec run`, `repo status`, `sync status`, `review create-project`.
|
|
90
91
|
- Lanes are independent clones: two lanes, or two agents, never share refs, an index, or a working tree.
|
|
91
92
|
- A lane commit that commits nothing anywhere refuses loudly instead of reporting success.
|
|
92
93
|
|
|
@@ -100,17 +101,18 @@ Not there yet:
|
|
|
100
101
|
|
|
101
102
|
| Group | Verbs |
|
|
102
103
|
|---|---|
|
|
103
|
-
| `workspace` | init, init-from-topology, materialize, status, convert-clone, detect-gr1, migrate-gr1, migrate-lane-state, bootstrap-gr1 |
|
|
104
|
+
| `workspace` | init, init-from-topology, materialize, status, convert-clone, detect-gr1, migrate-gr1, migrate-lane-state, bootstrap-gr1, gitinclude |
|
|
104
105
|
| `spec` | show, validate |
|
|
105
|
-
| `lane` | create, enter, exit,
|
|
106
|
-
| (top level) | branch, add, commit, push, prune |
|
|
106
|
+
| `lane` | create, enter, exit, show, resolve, bind, lease |
|
|
107
|
+
| (top level) | branch, add, commit, push, prune, status, plan, apply |
|
|
107
108
|
| `sync` | status, run |
|
|
108
|
-
| `pr` | create, status, checks, merge |
|
|
109
|
+
| `pr` | create, status, checks, merge, view |
|
|
109
110
|
| `review` | open, close, checkout-pr, run, requirements, bind, verify, rebind, create-project |
|
|
110
111
|
| `exec` | status, run |
|
|
111
|
-
| `repo` | status,
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
112
|
+
| `repo` | status, projection-run |
|
|
113
|
+
| `hooks` | trust, revoke, status, show, run |
|
|
114
|
+
| `store` | init, commit, check, push, status, log, diff, checkout, materialize, migrate |
|
|
115
|
+
| `target`, `config` | stored PR target; config overlays |
|
|
114
116
|
|
|
115
117
|
## Overlay substrate
|
|
116
118
|
|
|
@@ -123,3 +125,5 @@ pip install -e "gr2[dev]" # from the repository root
|
|
|
123
125
|
cd gr2 && pytest # the gr2 suite (tests + overlay tests)
|
|
124
126
|
cd gr2 && ruff check . # lint, same config as CI
|
|
125
127
|
```
|
|
128
|
+
|
|
129
|
+
The Rust development binary is **`gr2-dev`** — built by `cargo build -p gr2-cli --release`, `publish = false`, and not shipped with `cargo install gitgrip`. It answered to the name `gr2` until 2026-09-30, so a branch written before that rename goes red on `cargo test -p gr2-cli` with `CARGO_BIN_EXE_gr2 is unset`; the repair is `cargo_bin("gr2-dev")`, from which Cargo derives `CARGO_BIN_EXE_gr2-dev`.
|
|
@@ -61,7 +61,7 @@ gr2 commit -m "my change" --workspace-root ~/ws --owner-unit default
|
|
|
61
61
|
```bash
|
|
62
62
|
gr2 exec run ~/ws default --actor human:you -- pytest -q # one command, every lane repo
|
|
63
63
|
gr2 review create-project ~/ws default feat-x # prints gr:<sha>: one object to review
|
|
64
|
-
gr2 lane
|
|
64
|
+
gr2 lane show ~/ws default # where am I?
|
|
65
65
|
gr2 lane exit ~/ws default --actor human:you
|
|
66
66
|
```
|
|
67
67
|
|
|
@@ -71,7 +71,7 @@ gr2 lane exit ~/ws default --actor human:you
|
|
|
71
71
|
|
|
72
72
|
Alpha 2 is a working local workspace layer. Verified by running the walk above:
|
|
73
73
|
|
|
74
|
-
- `workspace` (init, materialize, status, migrate-gr1 from a gr1 gripspace), `lane` (create, enter, exit, current, lease), `store` (init,
|
|
74
|
+
- `workspace` (init, materialize, status, migrate-gr1 from a gr1 gripspace), `lane` (create, enter, exit, current, lease), `store` (init, commit, log, diff, checkout), `exec run`, `repo status`, `sync status`, `review create-project`.
|
|
75
75
|
- Lanes are independent clones: two lanes, or two agents, never share refs, an index, or a working tree.
|
|
76
76
|
- A lane commit that commits nothing anywhere refuses loudly instead of reporting success.
|
|
77
77
|
|
|
@@ -85,17 +85,18 @@ Not there yet:
|
|
|
85
85
|
|
|
86
86
|
| Group | Verbs |
|
|
87
87
|
|---|---|
|
|
88
|
-
| `workspace` | init, init-from-topology, materialize, status, convert-clone, detect-gr1, migrate-gr1, migrate-lane-state, bootstrap-gr1 |
|
|
88
|
+
| `workspace` | init, init-from-topology, materialize, status, convert-clone, detect-gr1, migrate-gr1, migrate-lane-state, bootstrap-gr1, gitinclude |
|
|
89
89
|
| `spec` | show, validate |
|
|
90
|
-
| `lane` | create, enter, exit,
|
|
91
|
-
| (top level) | branch, add, commit, push, prune |
|
|
90
|
+
| `lane` | create, enter, exit, show, resolve, bind, lease |
|
|
91
|
+
| (top level) | branch, add, commit, push, prune, status, plan, apply |
|
|
92
92
|
| `sync` | status, run |
|
|
93
|
-
| `pr` | create, status, checks, merge |
|
|
93
|
+
| `pr` | create, status, checks, merge, view |
|
|
94
94
|
| `review` | open, close, checkout-pr, run, requirements, bind, verify, rebind, create-project |
|
|
95
95
|
| `exec` | status, run |
|
|
96
|
-
| `repo` | status,
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
96
|
+
| `repo` | status, projection-run |
|
|
97
|
+
| `hooks` | trust, revoke, status, show, run |
|
|
98
|
+
| `store` | init, commit, check, push, status, log, diff, checkout, materialize, migrate |
|
|
99
|
+
| `target`, `config` | stored PR target; config overlays |
|
|
99
100
|
|
|
100
101
|
## Overlay substrate
|
|
101
102
|
|
|
@@ -108,3 +109,5 @@ pip install -e "gr2[dev]" # from the repository root
|
|
|
108
109
|
cd gr2 && pytest # the gr2 suite (tests + overlay tests)
|
|
109
110
|
cd gr2 && ruff check . # lint, same config as CI
|
|
110
111
|
```
|
|
112
|
+
|
|
113
|
+
The Rust development binary is **`gr2-dev`** — built by `cargo build -p gr2-cli --release`, `publish = false`, and not shipped with `cargo install gitgrip`. It answered to the name `gr2` until 2026-09-30, so a branch written before that rename goes red on `cargo test -p gr2-cli` with `CARGO_BIN_EXE_gr2 is unset`; the repair is `cargo_bin("gr2-dev")`, from which Cargo derives `CARGO_BIN_EXE_gr2-dev`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitgrip
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0a6
|
|
4
4
|
Summary: Workspace layer for multi-repo work: workspaces, isolated lanes, and one grouped review per slice
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -12,6 +12,7 @@ Requires-Dist: jsonschema>=4.18
|
|
|
12
12
|
Provides-Extra: dev
|
|
13
13
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
14
14
|
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
15
|
+
Requires-Dist: packaging>=22; extra == "dev"
|
|
15
16
|
|
|
16
17
|
# gr2
|
|
17
18
|
|
|
@@ -76,7 +77,7 @@ gr2 commit -m "my change" --workspace-root ~/ws --owner-unit default
|
|
|
76
77
|
```bash
|
|
77
78
|
gr2 exec run ~/ws default --actor human:you -- pytest -q # one command, every lane repo
|
|
78
79
|
gr2 review create-project ~/ws default feat-x # prints gr:<sha>: one object to review
|
|
79
|
-
gr2 lane
|
|
80
|
+
gr2 lane show ~/ws default # where am I?
|
|
80
81
|
gr2 lane exit ~/ws default --actor human:you
|
|
81
82
|
```
|
|
82
83
|
|
|
@@ -86,7 +87,7 @@ gr2 lane exit ~/ws default --actor human:you
|
|
|
86
87
|
|
|
87
88
|
Alpha 2 is a working local workspace layer. Verified by running the walk above:
|
|
88
89
|
|
|
89
|
-
- `workspace` (init, materialize, status, migrate-gr1 from a gr1 gripspace), `lane` (create, enter, exit, current, lease), `store` (init,
|
|
90
|
+
- `workspace` (init, materialize, status, migrate-gr1 from a gr1 gripspace), `lane` (create, enter, exit, current, lease), `store` (init, commit, log, diff, checkout), `exec run`, `repo status`, `sync status`, `review create-project`.
|
|
90
91
|
- Lanes are independent clones: two lanes, or two agents, never share refs, an index, or a working tree.
|
|
91
92
|
- A lane commit that commits nothing anywhere refuses loudly instead of reporting success.
|
|
92
93
|
|
|
@@ -100,17 +101,18 @@ Not there yet:
|
|
|
100
101
|
|
|
101
102
|
| Group | Verbs |
|
|
102
103
|
|---|---|
|
|
103
|
-
| `workspace` | init, init-from-topology, materialize, status, convert-clone, detect-gr1, migrate-gr1, migrate-lane-state, bootstrap-gr1 |
|
|
104
|
+
| `workspace` | init, init-from-topology, materialize, status, convert-clone, detect-gr1, migrate-gr1, migrate-lane-state, bootstrap-gr1, gitinclude |
|
|
104
105
|
| `spec` | show, validate |
|
|
105
|
-
| `lane` | create, enter, exit,
|
|
106
|
-
| (top level) | branch, add, commit, push, prune |
|
|
106
|
+
| `lane` | create, enter, exit, show, resolve, bind, lease |
|
|
107
|
+
| (top level) | branch, add, commit, push, prune, status, plan, apply |
|
|
107
108
|
| `sync` | status, run |
|
|
108
|
-
| `pr` | create, status, checks, merge |
|
|
109
|
+
| `pr` | create, status, checks, merge, view |
|
|
109
110
|
| `review` | open, close, checkout-pr, run, requirements, bind, verify, rebind, create-project |
|
|
110
111
|
| `exec` | status, run |
|
|
111
|
-
| `repo` | status,
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
112
|
+
| `repo` | status, projection-run |
|
|
113
|
+
| `hooks` | trust, revoke, status, show, run |
|
|
114
|
+
| `store` | init, commit, check, push, status, log, diff, checkout, materialize, migrate |
|
|
115
|
+
| `target`, `config` | stored PR target; config overlays |
|
|
114
116
|
|
|
115
117
|
## Overlay substrate
|
|
116
118
|
|
|
@@ -123,3 +125,5 @@ pip install -e "gr2[dev]" # from the repository root
|
|
|
123
125
|
cd gr2 && pytest # the gr2 suite (tests + overlay tests)
|
|
124
126
|
cd gr2 && ruff check . # lint, same config as CI
|
|
125
127
|
```
|
|
128
|
+
|
|
129
|
+
The Rust development binary is **`gr2-dev`** — built by `cargo build -p gr2-cli --release`, `publish = false`, and not shipped with `cargo install gitgrip`. It answered to the name `gr2` until 2026-09-30, so a branch written before that rename goes red on `cargo test -p gr2-cli` with `CARGO_BIN_EXE_gr2 is unset`; the repair is `cargo_bin("gr2-dev")`, from which Cargo derives `CARGO_BIN_EXE_gr2-dev`.
|
|
@@ -7,6 +7,7 @@ gitgrip.egg-info/entry_points.txt
|
|
|
7
7
|
gitgrip.egg-info/requires.txt
|
|
8
8
|
gitgrip.egg-info/top_level.txt
|
|
9
9
|
gr2/__init__.py
|
|
10
|
+
gr2/api_core.py
|
|
10
11
|
gr2/overlay/__init__.py
|
|
11
12
|
gr2/overlay/activate.py
|
|
12
13
|
gr2/overlay/agent_manifest.py
|
|
@@ -23,6 +24,7 @@ gr2/overlay/types.py
|
|
|
23
24
|
gr2/overlay/units.py
|
|
24
25
|
gr2/overlay/workspace_spec.py
|
|
25
26
|
gr2/schemas/gr2-materialization-plan-v1.schema.json
|
|
27
|
+
gr2/schemas/gr2-workspace-spec-v1.schema.json
|
|
26
28
|
gr2_overlay/__init__.py
|
|
27
29
|
prototypes/__init__.py
|
|
28
30
|
prototypes/cache_materialization_probe.py
|
|
@@ -59,6 +61,7 @@ python_cli/consent.py
|
|
|
59
61
|
python_cli/env_exec.py
|
|
60
62
|
python_cli/events.py
|
|
61
63
|
python_cli/execops.py
|
|
64
|
+
python_cli/exit_codes.py
|
|
62
65
|
python_cli/failures.py
|
|
63
66
|
python_cli/file_exec.py
|
|
64
67
|
python_cli/git_review.py
|
|
@@ -68,6 +71,7 @@ python_cli/grip.py
|
|
|
68
71
|
python_cli/grip_cli.py
|
|
69
72
|
python_cli/hooks.py
|
|
70
73
|
python_cli/launch_exec.py
|
|
74
|
+
python_cli/layout.py
|
|
71
75
|
python_cli/merge_verification.py
|
|
72
76
|
python_cli/migration.py
|
|
73
77
|
python_cli/open_gr_review.py
|
|
@@ -80,19 +84,26 @@ python_cli/review.py
|
|
|
80
84
|
python_cli/review_dispatch.py
|
|
81
85
|
python_cli/review_ephemeral.py
|
|
82
86
|
python_cli/review_rebind.py
|
|
87
|
+
python_cli/review_records.py
|
|
83
88
|
python_cli/review_run.py
|
|
84
89
|
python_cli/review_runners.py
|
|
85
90
|
python_cli/spec_apply.py
|
|
86
91
|
python_cli/staging_cleanup.py
|
|
87
92
|
python_cli/syncops.py
|
|
88
93
|
python_cli/target.py
|
|
94
|
+
python_cli/version.py
|
|
89
95
|
python_cli/workspace_guidance.py
|
|
90
96
|
python_cli/workspace_snapshot.py
|
|
91
97
|
tests/test_add.py
|
|
92
98
|
tests/test_agent_manifest_overlay_source.py
|
|
99
|
+
tests/test_api_core_seam.py
|
|
100
|
+
tests/test_api_dump.py
|
|
93
101
|
tests/test_app_event_outcomes.py
|
|
94
102
|
tests/test_apply_convergence.py
|
|
103
|
+
tests/test_apply_gr1_sibling_adoption.py
|
|
95
104
|
tests/test_branch.py
|
|
105
|
+
tests/test_break_17_unit_path_containment.py
|
|
106
|
+
tests/test_break_18_gr1_sibling_units.py
|
|
96
107
|
tests/test_channel_bridge.py
|
|
97
108
|
tests/test_cli_adapter_error_boundary.py
|
|
98
109
|
tests/test_cli_shape_witnesses.py
|
|
@@ -102,6 +113,7 @@ tests/test_config_base_path_guard.py
|
|
|
102
113
|
tests/test_config_overlay.py
|
|
103
114
|
tests/test_conftest_git_isolation.py
|
|
104
115
|
tests/test_contribution_protocol.py
|
|
116
|
+
tests/test_deprecation_registry.py
|
|
105
117
|
tests/test_ensure_lane_checkout_reference_clone.py
|
|
106
118
|
tests/test_env_materialization.py
|
|
107
119
|
tests/test_event_log_integrity.py
|
|
@@ -121,32 +133,41 @@ tests/test_gr2_packaging.py
|
|
|
121
133
|
tests/test_gr2_review_receipt.py
|
|
122
134
|
tests/test_grip_cli.py
|
|
123
135
|
tests/test_grip_hardening.py
|
|
124
|
-
tests/test_grip_object_model.py
|
|
125
136
|
tests/test_grip_snapshot.py
|
|
137
|
+
tests/test_grip_toml_schema_v1.py
|
|
126
138
|
tests/test_hook_events.py
|
|
127
139
|
tests/test_hook_hardening.py
|
|
128
140
|
tests/test_hooks_consent.py
|
|
141
|
+
tests/test_hooks_group_names.py
|
|
129
142
|
tests/test_jsonl_store.py
|
|
130
143
|
tests/test_lane_create_fork_base_cli.py
|
|
144
|
+
tests/test_lane_create_refusal_leaves_nothing.py
|
|
131
145
|
tests/test_lane_create_refuses_own_branch.py
|
|
146
|
+
tests/test_lane_enter_hook_warnings.py
|
|
132
147
|
tests/test_lane_lease_show_json.py
|
|
133
148
|
tests/test_lane_lifecycle_safety.py
|
|
134
149
|
tests/test_lane_repo_paths_and_all_skipped.py
|
|
150
|
+
tests/test_lane_show_name.py
|
|
135
151
|
tests/test_lane_toml_serialization.py
|
|
136
152
|
tests/test_launch_primitive.py
|
|
153
|
+
tests/test_layout_census.py
|
|
137
154
|
tests/test_m2_overlay_hindsight.py
|
|
138
155
|
tests/test_m2_overlay_mem0.py
|
|
139
156
|
tests/test_m2_overlay_zep.py
|
|
140
157
|
tests/test_materialization_plan_contract.py
|
|
158
|
+
tests/test_materialize_pin.py
|
|
159
|
+
tests/test_member_working_root_checkout_materialize.py
|
|
141
160
|
tests/test_merge_evidence.py
|
|
142
161
|
tests/test_merge_parent_verification.py
|
|
143
162
|
tests/test_migration.py
|
|
144
163
|
tests/test_migration_overlay_compatibility.py
|
|
145
164
|
tests/test_migration_unadopted_notice.py
|
|
165
|
+
tests/test_native_store_review_record_probes.py
|
|
146
166
|
tests/test_no_bare_python_cli_imports.py
|
|
147
167
|
tests/test_null_current_lane_refusal.py
|
|
148
168
|
tests/test_open_gr_enter.py
|
|
149
169
|
tests/test_open_project_refusal_reason_cli.py
|
|
170
|
+
tests/test_outbox_ack_after_effect.py
|
|
150
171
|
tests/test_overlay_acceptance.py
|
|
151
172
|
tests/test_overlay_activate.py
|
|
152
173
|
tests/test_overlay_cli.py
|
|
@@ -168,8 +189,12 @@ tests/test_overlay_tier_b_staging_discipline.py
|
|
|
168
189
|
tests/test_overlay_trust_model.py
|
|
169
190
|
tests/test_overlay_workspace_spec.py
|
|
170
191
|
tests/test_pr_events.py
|
|
192
|
+
tests/test_pr_group_legibility.py
|
|
193
|
+
tests/test_pr_merge_head_pin.py
|
|
171
194
|
tests/test_pr_merge_method.py
|
|
195
|
+
tests/test_pr_merge_missing_target.py
|
|
172
196
|
tests/test_pr_merge_partial_failure.py
|
|
197
|
+
tests/test_pr_view.py
|
|
173
198
|
tests/test_project_file_materialization.py
|
|
174
199
|
tests/test_project_review_range_reconstruction.py
|
|
175
200
|
tests/test_project_review_three_repo.py
|
|
@@ -180,11 +205,13 @@ tests/test_propagation_state_machine.py
|
|
|
180
205
|
tests/test_prune.py
|
|
181
206
|
tests/test_push.py
|
|
182
207
|
tests/test_readme_verbs_exist.py
|
|
208
|
+
tests/test_repo_path_read_through.py
|
|
183
209
|
tests/test_repo_status_linked_worktree.py
|
|
184
210
|
tests/test_repo_status_schema_version.py
|
|
185
211
|
tests/test_repo_status_symlinked_git_dir.py
|
|
186
212
|
tests/test_review_bind_fork_base.py
|
|
187
213
|
tests/test_review_bind_from_range.py
|
|
214
|
+
tests/test_review_bind_native_store.py
|
|
188
215
|
tests/test_review_bind_verify.py
|
|
189
216
|
tests/test_review_cli.py
|
|
190
217
|
tests/test_review_close_gr.py
|
|
@@ -193,8 +220,11 @@ tests/test_review_dispatch.py
|
|
|
193
220
|
tests/test_review_ephemeral.py
|
|
194
221
|
tests/test_review_open_lane.py
|
|
195
222
|
tests/test_review_rebind.py
|
|
223
|
+
tests/test_review_receipt_cleanup.py
|
|
196
224
|
tests/test_review_run.py
|
|
197
225
|
tests/test_review_run_import_isolation.py
|
|
226
|
+
tests/test_review_run_integrity_recheck.py
|
|
227
|
+
tests/test_review_run_multi_repo.py
|
|
198
228
|
tests/test_review_run_rerun_and_refusal_receipt.py
|
|
199
229
|
tests/test_review_runners.py
|
|
200
230
|
tests/test_review_verify_inside.py
|
|
@@ -205,8 +235,21 @@ tests/test_spec_apply_event_outcomes.py
|
|
|
205
235
|
tests/test_spec_declaration_ref_pin.py
|
|
206
236
|
tests/test_sprint21_sync_platform.py
|
|
207
237
|
tests/test_staging_cleanup.py
|
|
238
|
+
tests/test_store_break_attempts.py
|
|
239
|
+
tests/test_store_checkout_atomicity.py
|
|
240
|
+
tests/test_store_commit_noop.py
|
|
241
|
+
tests/test_store_declared_members.py
|
|
242
|
+
tests/test_store_git_native_smoke.py
|
|
243
|
+
tests/test_store_init_branch_inference.py
|
|
244
|
+
tests/test_store_log_adoption_boundary.py
|
|
245
|
+
tests/test_store_member_path_hygiene.py
|
|
246
|
+
tests/test_store_member_state.py
|
|
247
|
+
tests/test_store_migrate_branch_inference.py
|
|
248
|
+
tests/test_store_status_values.py
|
|
208
249
|
tests/test_store_verb_rename.py
|
|
250
|
+
tests/test_store_verb_witnesses.py
|
|
209
251
|
tests/test_submodule_member_predicate.py
|
|
252
|
+
tests/test_superproject_import.py
|
|
210
253
|
tests/test_sync_event_outcomes.py
|
|
211
254
|
tests/test_sync_fast_forward.py
|
|
212
255
|
tests/test_sync_fetch.py
|
|
@@ -221,6 +264,8 @@ tests/test_unit_apply_dry_run.py
|
|
|
221
264
|
tests/test_unit_manifest_schema.py
|
|
222
265
|
tests/test_unit_propose.py
|
|
223
266
|
tests/test_unit_topological_apply.py
|
|
267
|
+
tests/test_validate_repo_path_conflict.py
|
|
268
|
+
tests/test_version_identity.py
|
|
224
269
|
tests/test_version_static.py
|
|
225
270
|
tests/test_workspace_convert_clone.py
|
|
226
271
|
tests/test_workspace_edit_lease_cap.py
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""The generic core of the API dumper: walker, line format, marker lookup.
|
|
2
|
+
|
|
3
|
+
THE SEAM. This module knows four things -- how to walk a command tree, how an
|
|
4
|
+
``.api`` line is shaped, how a stability table is keyed, and how items are
|
|
5
|
+
GROUPED into blocks -- and it knows nothing about gr2: not its app, not its
|
|
6
|
+
tables, not its exit codes. Everything that names gr2 lives in the consumer
|
|
7
|
+
(``gr2/scripts/dump_api.py``), which REGISTERS the kinds it emits, supplies the
|
|
8
|
+
marker rules for them, and says which COMMAND each item belongs to (``Row.group``).
|
|
9
|
+
Grouping is a core rule over a consumer-supplied fact, which is the same
|
|
10
|
+
division as the marker table: the core owns the shape, the consumer owns the
|
|
11
|
+
meaning.
|
|
12
|
+
|
|
13
|
+
The boundary is a test, not a convention: ``tests/test_api_core_seam.py`` walks
|
|
14
|
+
this module's imports at any depth and fails if any of them reaches gr2, with
|
|
15
|
+
the plugin beside it as the control that proves the walker can fail.
|
|
16
|
+
|
|
17
|
+
WHY IT IS WORTH THE SPLIT. The dumper does not move to its own repo yet -- but
|
|
18
|
+
the regroup of the dump by command and subcommand is the moment to cut the
|
|
19
|
+
seam, so that the later extraction is a MOVE rather than a rewrite. A core with
|
|
20
|
+
no gr2 import can be lifted out with its tests; one that reaches into
|
|
21
|
+
``grip_cli`` for an exit prefix cannot.
|
|
22
|
+
|
|
23
|
+
WHAT IS NOT HERE YET, named rather than implied: the alias registry
|
|
24
|
+
(``grip_cli.STORE_MOUNTS`` is gr2's own mount table) and the three gates are
|
|
25
|
+
still on the plugin side, because both answer questions about gr2's surface
|
|
26
|
+
rather than about the format. They move when a second consumer exists to make
|
|
27
|
+
their generic shape decidable.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import tomllib
|
|
33
|
+
from collections.abc import Callable
|
|
34
|
+
from dataclasses import dataclass
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import NamedTuple
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Row(NamedTuple):
|
|
40
|
+
"""One item, before it is rendered.
|
|
41
|
+
|
|
42
|
+
``group`` is the COMMAND PATH the item belongs to, and the CONSUMER supplies
|
|
43
|
+
it rather than the core deriving one. Only the consumer knows what a command
|
|
44
|
+
is in its own tree: the walk answers for a verb and the flags beneath it, but
|
|
45
|
+
an exit code belongs to a GROUP and a layout path belongs to no verb at all.
|
|
46
|
+
Deriving it here would mean the core guessing at the shape of a spelling.
|
|
47
|
+
|
|
48
|
+
An EMPTY group means "attached to no command" and renders under the
|
|
49
|
+
``UNGROUPED`` heading. A heading is what separates one block from the next,
|
|
50
|
+
so the ungrouped block is given one rather than left as rows appended to
|
|
51
|
+
whichever command sorted before them.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
kind: str
|
|
55
|
+
spelling: str
|
|
56
|
+
hidden: bool = False
|
|
57
|
+
hidden_by: str | None = None
|
|
58
|
+
group: str = ""
|
|
59
|
+
|
|
60
|
+
# (kind, spelling) -> "may-change" | "reserved". An item absent here is stable.
|
|
61
|
+
Markers = dict[tuple[str, str], str]
|
|
62
|
+
|
|
63
|
+
# The spelling column is padded to this width so the stability markers line up
|
|
64
|
+
# and a diff reads as a change to one item rather than to the whole block.
|
|
65
|
+
COLUMN = 64
|
|
66
|
+
|
|
67
|
+
# The heading an item renders under when its consumer gave it no command.
|
|
68
|
+
#
|
|
69
|
+
# NAMED RATHER THAN LEFT BLANK, because a heading is the only thing separating
|
|
70
|
+
# one block from the next: an ungrouped row printed with NO heading lands
|
|
71
|
+
# directly under the last command's items and reads as one of them. The blank
|
|
72
|
+
# version looks tidier in the one case it helps -- a consumer that groups nothing
|
|
73
|
+
# -- and misleads in the case that matters, which is a consumer that groups
|
|
74
|
+
# almost everything and forgets one.
|
|
75
|
+
UNGROUPED = "(ungrouped)"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def walk(cmd, prefix: tuple[str, ...] = (), hidden_by: str | None = None):
|
|
79
|
+
"""Every leaf command, as (path, command, hidden_by).
|
|
80
|
+
|
|
81
|
+
Hidden is INHERITED: a command is hidden when its own flag says so OR any
|
|
82
|
+
ancestor group is hidden. Typer marks a sub-app hidden at the GROUP level
|
|
83
|
+
(``add_typer(app, name="grip", hidden=True)``), and the group's children
|
|
84
|
+
keep ``hidden=False`` of their own -- so reading only the leaf reports a
|
|
85
|
+
hidden group's whole subtree as part of the visible surface. Measured: the
|
|
86
|
+
``grip`` alias group is hidden and 28 of its items were dumped as visible.
|
|
87
|
+
|
|
88
|
+
``hidden_by`` NAMES the ancestor group that did the hiding, because the dump
|
|
89
|
+
carries no group row of its own: without the name, one group flag reads as
|
|
90
|
+
dozens of identical lines and the cause is invisible to a reader.
|
|
91
|
+
"""
|
|
92
|
+
subs = getattr(cmd, "commands", None)
|
|
93
|
+
if subs:
|
|
94
|
+
if getattr(cmd, "hidden", False) and prefix:
|
|
95
|
+
hidden_by = prefix[-1]
|
|
96
|
+
out: list[tuple[tuple[str, ...], object, str | None]] = []
|
|
97
|
+
for name, sub in subs.items():
|
|
98
|
+
out.extend(walk(sub, prefix + (name,), hidden_by))
|
|
99
|
+
return out
|
|
100
|
+
return [(prefix, cmd, hidden_by)]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def label(spelling: str, hidden: bool, hidden_by: str | None) -> str:
|
|
104
|
+
"""The label column for one item -- the text BEFORE any parsing strips it.
|
|
105
|
+
|
|
106
|
+
One home for the rule, because a gate that wants to ask a question about the
|
|
107
|
+
label TEXT (does it end in a space? how long is it?) cannot use the parsed
|
|
108
|
+
rows: a parser strips the padding, and the trailing space it would be asking
|
|
109
|
+
about is exactly what stripping removes. Measured -- a first version of
|
|
110
|
+
``test_no_label_ends_in_a_space`` read labels through a parser and could not
|
|
111
|
+
have failed for any input, because every label it saw had already been
|
|
112
|
+
stripped.
|
|
113
|
+
|
|
114
|
+
An inherited hide names the group that did it; a command hidden in its own
|
|
115
|
+
right is just ``(hidden)``. The dump carries no group row, so the name is the
|
|
116
|
+
only place the cause can live.
|
|
117
|
+
"""
|
|
118
|
+
if not hidden:
|
|
119
|
+
return spelling
|
|
120
|
+
return f"{spelling} (hidden:{hidden_by})" if hidden_by else f"{spelling} (hidden)"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def line(kind: str, label: str, marker: str) -> str:
|
|
124
|
+
"""One ``.api`` line: two fixed columns, with the separator GUARANTEED.
|
|
125
|
+
|
|
126
|
+
A label at or past ``COLUMN`` is emitted at its own length, so padding it to
|
|
127
|
+
``COLUMN`` adds nothing and the marker runs straight into the label. Every
|
|
128
|
+
label in this dump contains spaces (a flag reads ``verb --flag <str>``), so a
|
|
129
|
+
reader splitting the two columns then takes a space INSIDE the label as the
|
|
130
|
+
separator and recovers neither column.
|
|
131
|
+
|
|
132
|
+
Measured before this rule existed: the 65-char ``exit`` label rendered as
|
|
133
|
+
``...store verb: "may-change``, whose last space is the one inside the quoted
|
|
134
|
+
prefix. The columns came back as the label truncated at ``verb:`` and a
|
|
135
|
+
marker of ``"may-change``, the marker lookup then missed, and every exit line
|
|
136
|
+
silently read as ``stable``.
|
|
137
|
+
|
|
138
|
+
Pulled out of ``render`` so the width rule has ONE home and a synthetic long
|
|
139
|
+
label can be tested against it directly, rather than only through whichever
|
|
140
|
+
labels happen to reach ``COLUMN`` today.
|
|
141
|
+
|
|
142
|
+
ONE SHAPE THIS CANNOT FIX, because the ambiguity is on the reader's side of
|
|
143
|
+
the line: a label whose LAST character is a space is indistinguishable from
|
|
144
|
+
the padding, so a reader stripping the padding returns it one character
|
|
145
|
+
short. Guaranteeing the separator does not help -- the space is inside the
|
|
146
|
+
label, before it. No label ends in a space today, and
|
|
147
|
+
``test_no_label_ends_in_a_space`` keeps it that way.
|
|
148
|
+
"""
|
|
149
|
+
width = max(COLUMN, len(label) + 1)
|
|
150
|
+
return f"{kind:<6}{label:<{width}}{marker}"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def load_markers(path: Path) -> Markers:
|
|
154
|
+
"""(kind, spelling) -> 'may-change' | 'reserved', from a stability table.
|
|
155
|
+
|
|
156
|
+
An absent file is an empty table rather than an error: a checkout with no
|
|
157
|
+
stability table has made no promises, and every item reading ``stable``
|
|
158
|
+
there is the stated default. The entries are ``"<kind> <spelling>"``, split
|
|
159
|
+
on the FIRST space, because a spelling carries spaces of its own.
|
|
160
|
+
"""
|
|
161
|
+
if not path.exists():
|
|
162
|
+
return {}
|
|
163
|
+
data = tomllib.loads(path.read_text())
|
|
164
|
+
out: Markers = {}
|
|
165
|
+
for marker in ("may-change", "reserved"):
|
|
166
|
+
for entry in data.get(marker, []):
|
|
167
|
+
kind, _, spelling = entry.partition(" ")
|
|
168
|
+
out[(kind, spelling)] = marker
|
|
169
|
+
return out
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def plain_marker(kind: str) -> Callable[[str, Markers], str]:
|
|
173
|
+
"""The default marker rule for a kind: plain lookup, stable unless named.
|
|
174
|
+
|
|
175
|
+
Defaulting to stable is deliberate -- a new item is stable unless somebody
|
|
176
|
+
says otherwise in review -- and it is the rule most kinds want, so a
|
|
177
|
+
consumer registering one writes ``plain_marker("exit")`` rather than a
|
|
178
|
+
four-line closure that has to be read to be trusted.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def resolve(spelling: str, markers: Markers) -> str:
|
|
182
|
+
return markers.get((kind, spelling), "stable")
|
|
183
|
+
|
|
184
|
+
return resolve
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@dataclass(frozen=True)
|
|
188
|
+
class Kind:
|
|
189
|
+
"""One item kind, as the consumer registers it.
|
|
190
|
+
|
|
191
|
+
``items`` yields ``Row``s; ``marker`` answers for this kind alone, which is
|
|
192
|
+
the point of registering per kind -- the lookup rule for a ``json`` key path
|
|
193
|
+
(inherit the canonical verb's promise) is not the rule for an ``exit`` code
|
|
194
|
+
(plain lookup) and a single dispatching function had to know both.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
name: str
|
|
198
|
+
items: Callable[[], list[Row]]
|
|
199
|
+
marker: Callable[[str, Markers], str]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class Registry:
|
|
203
|
+
"""The kinds this dump carries, in the order the consumer registered them."""
|
|
204
|
+
|
|
205
|
+
def __init__(self) -> None:
|
|
206
|
+
self._kinds: list[Kind] = []
|
|
207
|
+
|
|
208
|
+
def register(
|
|
209
|
+
self,
|
|
210
|
+
name: str,
|
|
211
|
+
items: Callable[[], list[Row]],
|
|
212
|
+
marker: Callable[[str, Markers], str],
|
|
213
|
+
) -> Kind:
|
|
214
|
+
if any(kind.name == name for kind in self._kinds):
|
|
215
|
+
raise ValueError(f"kind {name!r} is already registered")
|
|
216
|
+
kind = Kind(name=name, items=items, marker=marker)
|
|
217
|
+
self._kinds.append(kind)
|
|
218
|
+
return kind
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def names(self) -> tuple[str, ...]:
|
|
222
|
+
return tuple(kind.name for kind in self._kinds)
|
|
223
|
+
|
|
224
|
+
def rows(self) -> list[Row]:
|
|
225
|
+
out: list[Row] = []
|
|
226
|
+
for kind in self._kinds:
|
|
227
|
+
out.extend(kind.items())
|
|
228
|
+
return out
|
|
229
|
+
|
|
230
|
+
def marker(self, kind: str, spelling: str, markers: Markers) -> str:
|
|
231
|
+
"""The marker for one item, from the kind that owns it.
|
|
232
|
+
|
|
233
|
+
An UNREGISTERED kind is refused rather than defaulted. Answering
|
|
234
|
+
``stable`` for a kind nobody registered would publish a promise no one
|
|
235
|
+
made, and it would do it silently -- the failure would look exactly like
|
|
236
|
+
an item the reviewers simply never named.
|
|
237
|
+
"""
|
|
238
|
+
for registered in self._kinds:
|
|
239
|
+
if registered.name == kind:
|
|
240
|
+
return registered.marker(spelling, markers)
|
|
241
|
+
raise KeyError(
|
|
242
|
+
f"no kind {kind!r} is registered; registered: {list(self.names)}"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def blocks(rows: list[Row]) -> list[tuple[str, list[Row]]]:
|
|
247
|
+
"""Rows grouped by command path, the groups in COMMAND-TREE order.
|
|
248
|
+
|
|
249
|
+
The ORDER is by path, which is what makes the file read as a tree: ``store``
|
|
250
|
+
before ``store init``, and both before ``store-x``, so a command's
|
|
251
|
+
subcommands sit with it rather than scattered by an unrelated key.
|
|
252
|
+
|
|
253
|
+
THE KEY IS WRITTEN ON THE PATH'S TOKENS, AND ON THIS DUMP THAT CHANGES
|
|
254
|
+
NOTHING -- measured, not assumed: the separator is a space (0x20), every
|
|
255
|
+
character a path component can begin with sorts above it, and token order and
|
|
256
|
+
joined-string order agree on all 88 real headings. It is written on tokens
|
|
257
|
+
because the RULE is what a reader auditing the order should find stated, and
|
|
258
|
+
citing a separator's incidental collation order as the reason is how a rule
|
|
259
|
+
becomes an accident nobody can check.
|
|
260
|
+
|
|
261
|
+
The UNGROUPED block sorts LAST, and THAT is the part the key actually earns:
|
|
262
|
+
it keys on the ABSENCE of tokens rather than on the empty string, which would
|
|
263
|
+
sort it first and open the file with the items that have no home.
|
|
264
|
+
"""
|
|
265
|
+
by_group: dict[str, list[Row]] = {}
|
|
266
|
+
for row in rows:
|
|
267
|
+
by_group.setdefault(row.group, []).append(row)
|
|
268
|
+
return sorted(
|
|
269
|
+
by_group.items(), key=lambda item: (not item[0].split(), item[0].split())
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def render(registry: Registry, markers: Markers | None = None) -> str:
|
|
274
|
+
"""The exact bytes of an ``.api`` file, terminated by one newline.
|
|
275
|
+
|
|
276
|
+
GROUPED BY COMMAND, not by kind. The first column is still the item kind and
|
|
277
|
+
each line still carries one item, but the ORDER is now the command tree: a
|
|
278
|
+
heading names a command and its items sit under it. The previous order was a
|
|
279
|
+
single flat lexicographic sort, so all 120 ``arg`` rows preceded all 215
|
|
280
|
+
``flag`` rows and no command's surface could be read in one place -- "what
|
|
281
|
+
does ``store init`` take" was four searches, one per kind.
|
|
282
|
+
|
|
283
|
+
WITHIN a block the rows are sorted by their rendered line, which is the old
|
|
284
|
+
(kind, then label) order: a diff then reads one command at a time, and an
|
|
285
|
+
item that moves shows up moving between two blocks rather than teleporting
|
|
286
|
+
to a different corner of the file.
|
|
287
|
+
|
|
288
|
+
The heading is a COMMENT (``# <command>``) rather than a row, so a reader
|
|
289
|
+
that parses item lines has one unambiguous shape to parse and the heading
|
|
290
|
+
cannot be mistaken for a kind. EVERY block gets one, the ungrouped block
|
|
291
|
+
included (``# (ungrouped)``): a heading is what separates one block from the
|
|
292
|
+
next, so the block without one is the block that gets misread.
|
|
293
|
+
"""
|
|
294
|
+
markers = {} if markers is None else markers
|
|
295
|
+
lines: list[str] = []
|
|
296
|
+
for group, rows in blocks(registry.rows()):
|
|
297
|
+
lines.append(f"# {group or UNGROUPED}")
|
|
298
|
+
lines += sorted(
|
|
299
|
+
line(
|
|
300
|
+
row.kind,
|
|
301
|
+
label(row.spelling, row.hidden, row.hidden_by),
|
|
302
|
+
registry.marker(row.kind, row.spelling, markers),
|
|
303
|
+
)
|
|
304
|
+
for row in rows
|
|
305
|
+
)
|
|
306
|
+
return "\n".join(lines) + "\n"
|