gitgrip 2.0.0a1__tar.gz → 2.0.0a3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. gitgrip-2.0.0a3/PKG-INFO +125 -0
  2. gitgrip-2.0.0a3/README.md +110 -0
  3. gitgrip-2.0.0a3/gitgrip.egg-info/PKG-INFO +125 -0
  4. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gitgrip.egg-info/SOURCES.txt +12 -0
  5. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/lane_workspace_prototype.py +129 -1
  6. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/repo_maintenance_prototype.py +355 -7
  7. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/pyproject.toml +4 -3
  8. gitgrip-2.0.0a3/python_cli/__main__.py +8 -0
  9. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/app.py +242 -19
  10. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/commit.py +88 -2
  11. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/execops.py +12 -1
  12. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/git_review.py +48 -11
  13. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/gitops.py +142 -13
  14. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/grip.py +3 -1
  15. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/grip_cli.py +5 -1
  16. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/migration.py +29 -0
  17. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/review_run.py +45 -11
  18. gitgrip-2.0.0a3/python_cli/review_runners.py +292 -0
  19. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/spec_apply.py +6 -1
  20. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/syncops.py +6 -1
  21. gitgrip-2.0.0a3/python_cli/workspace_guidance.py +22 -0
  22. gitgrip-2.0.0a3/tests/test_cli_adapter_error_boundary.py +226 -0
  23. gitgrip-2.0.0a3/tests/test_exec_apostrophe_and_oserror.py +279 -0
  24. gitgrip-2.0.0a3/tests/test_exec_single_string_and_materialize_plan.py +442 -0
  25. gitgrip-2.0.0a3/tests/test_first_five_minutes.py +56 -0
  26. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_git_review.py +93 -0
  27. gitgrip-2.0.0a3/tests/test_gr1_first_run_guidance.py +80 -0
  28. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_lane_create_fork_base_cli.py +7 -7
  29. gitgrip-2.0.0a3/tests/test_lane_create_refuses_own_branch.py +231 -0
  30. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_lane_lifecycle_safety.py +5 -0
  31. gitgrip-2.0.0a3/tests/test_lane_repo_paths_and_all_skipped.py +234 -0
  32. gitgrip-2.0.0a3/tests/test_migration_unadopted_notice.py +122 -0
  33. gitgrip-2.0.0a3/tests/test_readme_verbs_exist.py +151 -0
  34. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_cli.py +41 -0
  35. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_runners.py +168 -1
  36. gitgrip-2.0.0a3/tests/test_single_repo_cwd_refusal.py +158 -0
  37. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_version_static.py +2 -2
  38. gitgrip-2.0.0a3/tests/test_workspace_convert_clone.py +603 -0
  39. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_workspace_init_from_topology.py +2 -1
  40. gitgrip-2.0.0a3/tests/test_workspace_init_local_repos.py +120 -0
  41. gitgrip-2.0.0a1/PKG-INFO +0 -13
  42. gitgrip-2.0.0a1/README.md +0 -258
  43. gitgrip-2.0.0a1/gitgrip.egg-info/PKG-INFO +0 -13
  44. gitgrip-2.0.0a1/python_cli/__main__.py +0 -6
  45. gitgrip-2.0.0a1/python_cli/review_runners.py +0 -133
  46. gitgrip-2.0.0a1/tests/test_workspace_convert_clone.py +0 -211
  47. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gitgrip.egg-info/dependency_links.txt +0 -0
  48. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gitgrip.egg-info/entry_points.txt +0 -0
  49. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gitgrip.egg-info/requires.txt +0 -0
  50. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gitgrip.egg-info/top_level.txt +0 -0
  51. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/__init__.py +0 -0
  52. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/__init__.py +0 -0
  53. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/activate.py +0 -0
  54. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/agent_manifest.py +0 -0
  55. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/cli.py +0 -0
  56. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/cross_repo.py +0 -0
  57. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/drivers.py +0 -0
  58. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/introspection.py +0 -0
  59. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/language_drivers.py +0 -0
  60. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/objects.py +0 -0
  61. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/perf.py +0 -0
  62. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/refs.py +0 -0
  63. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/trust.py +0 -0
  64. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/types.py +0 -0
  65. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/units.py +0 -0
  66. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/overlay/workspace_spec.py +0 -0
  67. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2/schemas/gr2-materialization-plan-v1.schema.json +0 -0
  68. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/gr2_overlay/__init__.py +0 -0
  69. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/__init__.py +0 -0
  70. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/cache_materialization_probe.py +0 -0
  71. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/concurrent_event_stress.py +0 -0
  72. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/concurrent_lease_stress.py +0 -0
  73. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/concurrent_workspace_cap_stress.py +0 -0
  74. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/contribution_protocol.py +0 -0
  75. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/cross_mode_lane_stress.py +0 -0
  76. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/jsonl_store.py +0 -0
  77. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/layout_model_probe.py +0 -0
  78. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/propagation_daemon.py +0 -0
  79. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/propagation_state_machine.py +0 -0
  80. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/python_exec_playground.py +0 -0
  81. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/python_hook_runtime_playground.py +0 -0
  82. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/python_migration_playground.py +0 -0
  83. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/python_review_checkout_playground.py +0 -0
  84. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/python_spec_apply_playground.py +0 -0
  85. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/real_git_lane_materialization.py +0 -0
  86. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/real_git_playground.py +0 -0
  87. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/recall_lane_history.py +0 -0
  88. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/prototypes/repo_transport_probe.py +0 -0
  89. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/__init__.py +0 -0
  90. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/add.py +0 -0
  91. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/branch.py +0 -0
  92. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/channel_bridge.py +0 -0
  93. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/clone_exec.py +0 -0
  94. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/config.py +0 -0
  95. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/env_exec.py +0 -0
  96. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/events.py +0 -0
  97. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/failures.py +0 -0
  98. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/file_exec.py +0 -0
  99. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/hooks.py +0 -0
  100. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/launch_exec.py +0 -0
  101. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/merge_verification.py +0 -0
  102. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/open_gr_review.py +0 -0
  103. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/platform.py +0 -0
  104. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/pr.py +0 -0
  105. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/project_review.py +0 -0
  106. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/prune.py +0 -0
  107. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/push.py +0 -0
  108. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/review.py +0 -0
  109. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/review_dispatch.py +0 -0
  110. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/review_ephemeral.py +0 -0
  111. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/review_rebind.py +0 -0
  112. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/staging_cleanup.py +0 -0
  113. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/target.py +0 -0
  114. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/python_cli/workspace_snapshot.py +0 -0
  115. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/setup.cfg +0 -0
  116. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_add.py +0 -0
  117. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_agent_manifest_overlay_source.py +0 -0
  118. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_app_event_outcomes.py +0 -0
  119. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_apply_convergence.py +0 -0
  120. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_branch.py +0 -0
  121. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_channel_bridge.py +0 -0
  122. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_clone_materialization.py +0 -0
  123. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_commit.py +0 -0
  124. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_config_base_path_guard.py +0 -0
  125. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_config_overlay.py +0 -0
  126. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_conftest_git_isolation.py +0 -0
  127. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_contribution_protocol.py +0 -0
  128. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_ensure_lane_checkout_reference_clone.py +0 -0
  129. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_env_materialization.py +0 -0
  130. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_event_log_integrity.py +0 -0
  131. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_event_outcome_policy.py +0 -0
  132. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_events.py +0 -0
  133. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_events_torn_line.py +0 -0
  134. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_execops.py +0 -0
  135. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_failure_event_outcome.py +0 -0
  136. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_foreground_launch_runtime.py +0 -0
  137. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_gr2_packaging.py +0 -0
  138. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_gr2_review_receipt.py +0 -0
  139. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_grip_cli.py +0 -0
  140. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_grip_hardening.py +0 -0
  141. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_grip_object_model.py +0 -0
  142. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_grip_snapshot.py +0 -0
  143. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_hook_events.py +0 -0
  144. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_hook_hardening.py +0 -0
  145. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_jsonl_store.py +0 -0
  146. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_lane_lease_show_json.py +0 -0
  147. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_lane_toml_serialization.py +0 -0
  148. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_launch_primitive.py +0 -0
  149. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_m2_overlay_hindsight.py +0 -0
  150. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_m2_overlay_mem0.py +0 -0
  151. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_m2_overlay_zep.py +0 -0
  152. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_materialization_plan_contract.py +0 -0
  153. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_merge_evidence.py +0 -0
  154. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_merge_parent_verification.py +0 -0
  155. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_migration.py +0 -0
  156. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_migration_overlay_compatibility.py +0 -0
  157. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_no_bare_python_cli_imports.py +0 -0
  158. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_null_current_lane_refusal.py +0 -0
  159. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_open_gr_enter.py +0 -0
  160. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_open_project_refusal_reason_cli.py +0 -0
  161. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_acceptance.py +0 -0
  162. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_activate.py +0 -0
  163. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_cli.py +0 -0
  164. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_cli_wiring.py +0 -0
  165. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_composition_merge.py +0 -0
  166. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_cross_repo_atomic_apply.py +0 -0
  167. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_cross_repo_perf_gate.py +0 -0
  168. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_cross_repo_rollback.py +0 -0
  169. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_driver_registry.py +0 -0
  170. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_introspection.py +0 -0
  171. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_multi_stack.py +0 -0
  172. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_object_encoding.py +0 -0
  173. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_perf_gates.py +0 -0
  174. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_python_driver.py +0 -0
  175. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_tier_b_encoding.py +0 -0
  176. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_tier_b_perf_gates.py +0 -0
  177. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_tier_b_repo_state.py +0 -0
  178. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_tier_b_staging_discipline.py +0 -0
  179. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_trust_model.py +0 -0
  180. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_overlay_workspace_spec.py +0 -0
  181. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_pr_events.py +0 -0
  182. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_pr_merge_method.py +0 -0
  183. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_pr_merge_partial_failure.py +0 -0
  184. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_project_file_materialization.py +0 -0
  185. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_project_review_range_reconstruction.py +0 -0
  186. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_project_review_three_repo.py +0 -0
  187. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_projection_tracked_dest.py +0 -0
  188. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_propagation_contribution.py +0 -0
  189. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_propagation_daemon.py +0 -0
  190. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_propagation_state_machine.py +0 -0
  191. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_prune.py +0 -0
  192. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_push.py +0 -0
  193. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_repo_status_linked_worktree.py +0 -0
  194. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_repo_status_schema_version.py +0 -0
  195. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_repo_status_symlinked_git_dir.py +0 -0
  196. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_bind_fork_base.py +0 -0
  197. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_bind_from_range.py +0 -0
  198. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_bind_verify.py +0 -0
  199. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_close_gr.py +0 -0
  200. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_create_project_cli.py +0 -0
  201. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_dispatch.py +0 -0
  202. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_ephemeral.py +0 -0
  203. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_open_lane.py +0 -0
  204. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_rebind.py +0 -0
  205. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_run.py +0 -0
  206. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_run_import_isolation.py +0 -0
  207. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_run_rerun_and_refusal_receipt.py +0 -0
  208. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_review_verify_inside.py +0 -0
  209. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_rmtree_or_refuse.py +0 -0
  210. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_snapshot_id_prefix_resolution.py +0 -0
  211. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_spec_apply_event_outcomes.py +0 -0
  212. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_sprint21_sync_platform.py +0 -0
  213. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_staging_cleanup.py +0 -0
  214. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_store_verb_rename.py +0 -0
  215. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_sync_event_outcomes.py +0 -0
  216. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_sync_fast_forward.py +0 -0
  217. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_sync_fetch.py +0 -0
  218. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_sync_seed_local.py +0 -0
  219. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_sync_ssh_rewrite.py +0 -0
  220. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_sync_status_probe.py +0 -0
  221. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_target.py +0 -0
  222. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_tmux_launch_runtime.py +0 -0
  223. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_unit_abort.py +0 -0
  224. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_unit_apply.py +0 -0
  225. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_unit_apply_dry_run.py +0 -0
  226. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_unit_manifest_schema.py +0 -0
  227. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_unit_propose.py +0 -0
  228. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_unit_topological_apply.py +0 -0
  229. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_workspace_edit_lease_cap.py +0 -0
  230. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_workspace_identity_boundary.py +0 -0
  231. {gitgrip-2.0.0a1 → gitgrip-2.0.0a3}/tests/test_workspace_snapshot.py +0 -0
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: gitgrip
3
+ Version: 2.0.0a3
4
+ Summary: Workspace layer for multi-repo work: workspaces, isolated lanes, and one grouped review per slice
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: typer>=0.9
8
+ Requires-Dist: tomli_w>=1.0
9
+ Requires-Dist: pyyaml>=6.0
10
+ Requires-Dist: toml>=0.10
11
+ Requires-Dist: jsonschema>=4.18
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=8.0; extra == "dev"
14
+ Requires-Dist: ruff>=0.4; extra == "dev"
15
+
16
+ # gr2
17
+
18
+ **gr2 is the workspace layer for multi-repo work.** One repository is just git; when a change spans repositories — a feature slice, a release, several agents touching several repos — gr2 gives you a workspace over them, isolated lanes to work in, and one grouped review/PR set for the slice. It is the Python successor to the Rust `gr` (1.x, called gr1 below); both ship from this repository, and `gr2` is the command for the workspace and the slice.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ uv tool install --pre gitgrip # the PyPI package is "gitgrip"; the command is "gr2"
24
+ gr2 --version
25
+ ```
26
+
27
+ Without uv, a venv and pip do the same:
28
+
29
+ ```bash
30
+ python3 -m venv ~/gr2-venv && source ~/gr2-venv/bin/activate
31
+ pip install --pre gitgrip
32
+ gr2 --version
33
+ ```
34
+
35
+ `--pre` is required while every gr2 release is an alpha: 1.5.0 and 1.5.1 were pre-alpha builds and are yanked, so without `--pre` there is nothing to install.
36
+
37
+ Already installed 1.5.0 or 1.5.1? The install commands above leave it in place, because an installed version already satisfies them. Upgrade explicitly: `uv tool upgrade --prerelease allow gitgrip`, or `pip install --pre -U gitgrip`. To run unreleased development code instead, install from a checkout: `pip install -e gr2/` from the repository root.
38
+
39
+ The Rust `gr` (gr1, 1.x) installs with `brew install synapt-dev/tap/gitgrip` or `cargo install gitgrip`. The two do not collide; you can have both installed.
40
+
41
+ ## First five minutes
42
+
43
+ Every command below was run, in this order, in a clean virtualenv against a throwaway workspace of two repositories. Put two clones side by side in one folder, say `~/ws/repo-a` and `~/ws/repo-b`.
44
+
45
+ ```bash
46
+ cd ~/ws
47
+ gr2 workspace init ~/ws # scans the folder; writes .grip/workspace_spec.toml
48
+ gr2 workspace materialize ~/ws --yes # builds the .grip layout and a default unit
49
+ gr2 workspace status ~/ws # what kind of workspace this is
50
+ gr2 store init ~/ws # the workspace snapshot store (needed before review)
51
+ ```
52
+
53
+ A **unit** is one worker — you, or one agent — and `default` is created for you. A **lane** is one piece of work spanning repos, with its own clones, so two lanes never step on each other:
54
+
55
+ ```bash
56
+ gr2 lane create ~/ws default feat-x --repos repo-a,repo-b --branch feat/x
57
+ gr2 lane enter ~/ws default feat-x --actor human:you
58
+ ```
59
+
60
+ Both commands print, per repo, the absolute directory you work in:
61
+
62
+ ```
63
+ repo-a: ~/ws/.grip/state/lanes/default/feat-x/repos/repo-a
64
+ repo-b: ~/ws/.grip/state/lanes/default/feat-x/repos/repo-b
65
+ ```
66
+
67
+ Edit there — not in your original clones. Stage and commit across the lane's repos:
68
+
69
+ ```bash
70
+ gr2 add . --repo-path ~/ws/.grip/state/lanes/default/feat-x/repos/repo-a
71
+ gr2 commit -m "my change" --workspace-root ~/ws --owner-unit default
72
+ ```
73
+
74
+ `commit` with the lane flags commits every lane repo that has staged changes and names the repos it skipped. If every repo was skipped it exits non-zero with one sentence naming where it looked — and, when it can see them, where your staged changes actually are. Run one command across every lane repo, then turn the lane into one reviewable object:
75
+
76
+ ```bash
77
+ gr2 exec run ~/ws default --actor human:you -- pytest -q # one command, every lane repo
78
+ gr2 review create-project ~/ws default feat-x # prints gr:<sha>: one object to review
79
+ gr2 lane current ~/ws default # where am I?
80
+ gr2 lane exit ~/ws default --actor human:you
81
+ ```
82
+
83
+ `exec run` and `lane exit` require `--actor`. Every group and verb takes `--help`; `gr2 --help` lists them all.
84
+
85
+ ## What works today, and what is not there yet
86
+
87
+ Alpha 2 is a working local workspace layer. Verified by running the walk above:
88
+
89
+ - `workspace` (init, materialize, status, migrate-gr1 from a gr1 gripspace), `lane` (create, enter, exit, current, lease), `store` (init, snapshot, log, diff, checkout), `exec run`, `repo status`, `sync status`, `review create-project`.
90
+ - Lanes are independent clones: two lanes, or two agents, never share refs, an index, or a working tree.
91
+ - A lane commit that commits nothing anywhere refuses loudly instead of reporting success.
92
+
93
+ Not there yet:
94
+
95
+ - The full walk with **remote** PRs (`gr2 push`, `gr2 pr create`) is documented in `--help` but was not exercised in a clean environment for this README; treat those two as alpha.
96
+ - `gr2 --help` and some verb output still say "prototype" in places; the wording is being cleaned up as verbs land.
97
+ - The Rust `gr` (gr1) still owns `spawn`, `fleet`, and `manifest` until their gr2 ports land. Everything else — workspace sync, lanes, the multi-repo slice, its review — is gr2's job; if a verb is missing or wrong there, that is a bug worth reporting, not a reason to reach for gr1.
98
+
99
+ ## Commands
100
+
101
+ | Group | Verbs |
102
+ |---|---|
103
+ | `workspace` | init, init-from-topology, materialize, status, convert-clone, detect-gr1, migrate-gr1, migrate-lane-state, bootstrap-gr1 |
104
+ | `spec` | show, validate |
105
+ | `lane` | create, enter, exit, current, resolve, bind, lease |
106
+ | (top level) | branch, add, commit, push, prune |
107
+ | `sync` | status, run |
108
+ | `pr` | create, status, checks, merge |
109
+ | `review` | open, close, checkout-pr, run, requirements, bind, verify, rebind, create-project |
110
+ | `exec` | status, run |
111
+ | `repo` | status, hooks, hook-run, projection-run |
112
+ | `store` | init, snapshot, log, diff, checkout |
113
+ | `target`, `config`, `plan`, `apply` | stored PR target; config overlays; show/apply the materialization plan |
114
+
115
+ ## Overlay substrate
116
+
117
+ gr2 also carries the config-overlay substrate (capture, compose, and materialize configuration layers over a workspace): see [docs/OVERLAY-SUBSTRATE.md](docs/OVERLAY-SUBSTRATE.md).
118
+
119
+ ## Development
120
+
121
+ ```bash
122
+ pip install -e "gr2[dev]" # from the repository root
123
+ cd gr2 && pytest # the gr2 suite (tests + overlay tests)
124
+ cd gr2 && ruff check . # lint, same config as CI
125
+ ```
@@ -0,0 +1,110 @@
1
+ # gr2
2
+
3
+ **gr2 is the workspace layer for multi-repo work.** One repository is just git; when a change spans repositories — a feature slice, a release, several agents touching several repos — gr2 gives you a workspace over them, isolated lanes to work in, and one grouped review/PR set for the slice. It is the Python successor to the Rust `gr` (1.x, called gr1 below); both ship from this repository, and `gr2` is the command for the workspace and the slice.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ uv tool install --pre gitgrip # the PyPI package is "gitgrip"; the command is "gr2"
9
+ gr2 --version
10
+ ```
11
+
12
+ Without uv, a venv and pip do the same:
13
+
14
+ ```bash
15
+ python3 -m venv ~/gr2-venv && source ~/gr2-venv/bin/activate
16
+ pip install --pre gitgrip
17
+ gr2 --version
18
+ ```
19
+
20
+ `--pre` is required while every gr2 release is an alpha: 1.5.0 and 1.5.1 were pre-alpha builds and are yanked, so without `--pre` there is nothing to install.
21
+
22
+ Already installed 1.5.0 or 1.5.1? The install commands above leave it in place, because an installed version already satisfies them. Upgrade explicitly: `uv tool upgrade --prerelease allow gitgrip`, or `pip install --pre -U gitgrip`. To run unreleased development code instead, install from a checkout: `pip install -e gr2/` from the repository root.
23
+
24
+ The Rust `gr` (gr1, 1.x) installs with `brew install synapt-dev/tap/gitgrip` or `cargo install gitgrip`. The two do not collide; you can have both installed.
25
+
26
+ ## First five minutes
27
+
28
+ Every command below was run, in this order, in a clean virtualenv against a throwaway workspace of two repositories. Put two clones side by side in one folder, say `~/ws/repo-a` and `~/ws/repo-b`.
29
+
30
+ ```bash
31
+ cd ~/ws
32
+ gr2 workspace init ~/ws # scans the folder; writes .grip/workspace_spec.toml
33
+ gr2 workspace materialize ~/ws --yes # builds the .grip layout and a default unit
34
+ gr2 workspace status ~/ws # what kind of workspace this is
35
+ gr2 store init ~/ws # the workspace snapshot store (needed before review)
36
+ ```
37
+
38
+ A **unit** is one worker — you, or one agent — and `default` is created for you. A **lane** is one piece of work spanning repos, with its own clones, so two lanes never step on each other:
39
+
40
+ ```bash
41
+ gr2 lane create ~/ws default feat-x --repos repo-a,repo-b --branch feat/x
42
+ gr2 lane enter ~/ws default feat-x --actor human:you
43
+ ```
44
+
45
+ Both commands print, per repo, the absolute directory you work in:
46
+
47
+ ```
48
+ repo-a: ~/ws/.grip/state/lanes/default/feat-x/repos/repo-a
49
+ repo-b: ~/ws/.grip/state/lanes/default/feat-x/repos/repo-b
50
+ ```
51
+
52
+ Edit there — not in your original clones. Stage and commit across the lane's repos:
53
+
54
+ ```bash
55
+ gr2 add . --repo-path ~/ws/.grip/state/lanes/default/feat-x/repos/repo-a
56
+ gr2 commit -m "my change" --workspace-root ~/ws --owner-unit default
57
+ ```
58
+
59
+ `commit` with the lane flags commits every lane repo that has staged changes and names the repos it skipped. If every repo was skipped it exits non-zero with one sentence naming where it looked — and, when it can see them, where your staged changes actually are. Run one command across every lane repo, then turn the lane into one reviewable object:
60
+
61
+ ```bash
62
+ gr2 exec run ~/ws default --actor human:you -- pytest -q # one command, every lane repo
63
+ gr2 review create-project ~/ws default feat-x # prints gr:<sha>: one object to review
64
+ gr2 lane current ~/ws default # where am I?
65
+ gr2 lane exit ~/ws default --actor human:you
66
+ ```
67
+
68
+ `exec run` and `lane exit` require `--actor`. Every group and verb takes `--help`; `gr2 --help` lists them all.
69
+
70
+ ## What works today, and what is not there yet
71
+
72
+ Alpha 2 is a working local workspace layer. Verified by running the walk above:
73
+
74
+ - `workspace` (init, materialize, status, migrate-gr1 from a gr1 gripspace), `lane` (create, enter, exit, current, lease), `store` (init, snapshot, log, diff, checkout), `exec run`, `repo status`, `sync status`, `review create-project`.
75
+ - Lanes are independent clones: two lanes, or two agents, never share refs, an index, or a working tree.
76
+ - A lane commit that commits nothing anywhere refuses loudly instead of reporting success.
77
+
78
+ Not there yet:
79
+
80
+ - The full walk with **remote** PRs (`gr2 push`, `gr2 pr create`) is documented in `--help` but was not exercised in a clean environment for this README; treat those two as alpha.
81
+ - `gr2 --help` and some verb output still say "prototype" in places; the wording is being cleaned up as verbs land.
82
+ - The Rust `gr` (gr1) still owns `spawn`, `fleet`, and `manifest` until their gr2 ports land. Everything else — workspace sync, lanes, the multi-repo slice, its review — is gr2's job; if a verb is missing or wrong there, that is a bug worth reporting, not a reason to reach for gr1.
83
+
84
+ ## Commands
85
+
86
+ | Group | Verbs |
87
+ |---|---|
88
+ | `workspace` | init, init-from-topology, materialize, status, convert-clone, detect-gr1, migrate-gr1, migrate-lane-state, bootstrap-gr1 |
89
+ | `spec` | show, validate |
90
+ | `lane` | create, enter, exit, current, resolve, bind, lease |
91
+ | (top level) | branch, add, commit, push, prune |
92
+ | `sync` | status, run |
93
+ | `pr` | create, status, checks, merge |
94
+ | `review` | open, close, checkout-pr, run, requirements, bind, verify, rebind, create-project |
95
+ | `exec` | status, run |
96
+ | `repo` | status, hooks, hook-run, projection-run |
97
+ | `store` | init, snapshot, log, diff, checkout |
98
+ | `target`, `config`, `plan`, `apply` | stored PR target; config overlays; show/apply the materialization plan |
99
+
100
+ ## Overlay substrate
101
+
102
+ gr2 also carries the config-overlay substrate (capture, compose, and materialize configuration layers over a workspace): see [docs/OVERLAY-SUBSTRATE.md](docs/OVERLAY-SUBSTRATE.md).
103
+
104
+ ## Development
105
+
106
+ ```bash
107
+ pip install -e "gr2[dev]" # from the repository root
108
+ cd gr2 && pytest # the gr2 suite (tests + overlay tests)
109
+ cd gr2 && ruff check . # lint, same config as CI
110
+ ```
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: gitgrip
3
+ Version: 2.0.0a3
4
+ Summary: Workspace layer for multi-repo work: workspaces, isolated lanes, and one grouped review per slice
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: typer>=0.9
8
+ Requires-Dist: tomli_w>=1.0
9
+ Requires-Dist: pyyaml>=6.0
10
+ Requires-Dist: toml>=0.10
11
+ Requires-Dist: jsonschema>=4.18
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=8.0; extra == "dev"
14
+ Requires-Dist: ruff>=0.4; extra == "dev"
15
+
16
+ # gr2
17
+
18
+ **gr2 is the workspace layer for multi-repo work.** One repository is just git; when a change spans repositories — a feature slice, a release, several agents touching several repos — gr2 gives you a workspace over them, isolated lanes to work in, and one grouped review/PR set for the slice. It is the Python successor to the Rust `gr` (1.x, called gr1 below); both ship from this repository, and `gr2` is the command for the workspace and the slice.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ uv tool install --pre gitgrip # the PyPI package is "gitgrip"; the command is "gr2"
24
+ gr2 --version
25
+ ```
26
+
27
+ Without uv, a venv and pip do the same:
28
+
29
+ ```bash
30
+ python3 -m venv ~/gr2-venv && source ~/gr2-venv/bin/activate
31
+ pip install --pre gitgrip
32
+ gr2 --version
33
+ ```
34
+
35
+ `--pre` is required while every gr2 release is an alpha: 1.5.0 and 1.5.1 were pre-alpha builds and are yanked, so without `--pre` there is nothing to install.
36
+
37
+ Already installed 1.5.0 or 1.5.1? The install commands above leave it in place, because an installed version already satisfies them. Upgrade explicitly: `uv tool upgrade --prerelease allow gitgrip`, or `pip install --pre -U gitgrip`. To run unreleased development code instead, install from a checkout: `pip install -e gr2/` from the repository root.
38
+
39
+ The Rust `gr` (gr1, 1.x) installs with `brew install synapt-dev/tap/gitgrip` or `cargo install gitgrip`. The two do not collide; you can have both installed.
40
+
41
+ ## First five minutes
42
+
43
+ Every command below was run, in this order, in a clean virtualenv against a throwaway workspace of two repositories. Put two clones side by side in one folder, say `~/ws/repo-a` and `~/ws/repo-b`.
44
+
45
+ ```bash
46
+ cd ~/ws
47
+ gr2 workspace init ~/ws # scans the folder; writes .grip/workspace_spec.toml
48
+ gr2 workspace materialize ~/ws --yes # builds the .grip layout and a default unit
49
+ gr2 workspace status ~/ws # what kind of workspace this is
50
+ gr2 store init ~/ws # the workspace snapshot store (needed before review)
51
+ ```
52
+
53
+ A **unit** is one worker — you, or one agent — and `default` is created for you. A **lane** is one piece of work spanning repos, with its own clones, so two lanes never step on each other:
54
+
55
+ ```bash
56
+ gr2 lane create ~/ws default feat-x --repos repo-a,repo-b --branch feat/x
57
+ gr2 lane enter ~/ws default feat-x --actor human:you
58
+ ```
59
+
60
+ Both commands print, per repo, the absolute directory you work in:
61
+
62
+ ```
63
+ repo-a: ~/ws/.grip/state/lanes/default/feat-x/repos/repo-a
64
+ repo-b: ~/ws/.grip/state/lanes/default/feat-x/repos/repo-b
65
+ ```
66
+
67
+ Edit there — not in your original clones. Stage and commit across the lane's repos:
68
+
69
+ ```bash
70
+ gr2 add . --repo-path ~/ws/.grip/state/lanes/default/feat-x/repos/repo-a
71
+ gr2 commit -m "my change" --workspace-root ~/ws --owner-unit default
72
+ ```
73
+
74
+ `commit` with the lane flags commits every lane repo that has staged changes and names the repos it skipped. If every repo was skipped it exits non-zero with one sentence naming where it looked — and, when it can see them, where your staged changes actually are. Run one command across every lane repo, then turn the lane into one reviewable object:
75
+
76
+ ```bash
77
+ gr2 exec run ~/ws default --actor human:you -- pytest -q # one command, every lane repo
78
+ gr2 review create-project ~/ws default feat-x # prints gr:<sha>: one object to review
79
+ gr2 lane current ~/ws default # where am I?
80
+ gr2 lane exit ~/ws default --actor human:you
81
+ ```
82
+
83
+ `exec run` and `lane exit` require `--actor`. Every group and verb takes `--help`; `gr2 --help` lists them all.
84
+
85
+ ## What works today, and what is not there yet
86
+
87
+ Alpha 2 is a working local workspace layer. Verified by running the walk above:
88
+
89
+ - `workspace` (init, materialize, status, migrate-gr1 from a gr1 gripspace), `lane` (create, enter, exit, current, lease), `store` (init, snapshot, log, diff, checkout), `exec run`, `repo status`, `sync status`, `review create-project`.
90
+ - Lanes are independent clones: two lanes, or two agents, never share refs, an index, or a working tree.
91
+ - A lane commit that commits nothing anywhere refuses loudly instead of reporting success.
92
+
93
+ Not there yet:
94
+
95
+ - The full walk with **remote** PRs (`gr2 push`, `gr2 pr create`) is documented in `--help` but was not exercised in a clean environment for this README; treat those two as alpha.
96
+ - `gr2 --help` and some verb output still say "prototype" in places; the wording is being cleaned up as verbs land.
97
+ - The Rust `gr` (gr1) still owns `spawn`, `fleet`, and `manifest` until their gr2 ports land. Everything else — workspace sync, lanes, the multi-repo slice, its review — is gr2's job; if a verb is missing or wrong there, that is a bug worth reporting, not a reason to reach for gr1.
98
+
99
+ ## Commands
100
+
101
+ | Group | Verbs |
102
+ |---|---|
103
+ | `workspace` | init, init-from-topology, materialize, status, convert-clone, detect-gr1, migrate-gr1, migrate-lane-state, bootstrap-gr1 |
104
+ | `spec` | show, validate |
105
+ | `lane` | create, enter, exit, current, resolve, bind, lease |
106
+ | (top level) | branch, add, commit, push, prune |
107
+ | `sync` | status, run |
108
+ | `pr` | create, status, checks, merge |
109
+ | `review` | open, close, checkout-pr, run, requirements, bind, verify, rebind, create-project |
110
+ | `exec` | status, run |
111
+ | `repo` | status, hooks, hook-run, projection-run |
112
+ | `store` | init, snapshot, log, diff, checkout |
113
+ | `target`, `config`, `plan`, `apply` | stored PR target; config overlays; show/apply the materialization plan |
114
+
115
+ ## Overlay substrate
116
+
117
+ gr2 also carries the config-overlay substrate (capture, compose, and materialize configuration layers over a workspace): see [docs/OVERLAY-SUBSTRATE.md](docs/OVERLAY-SUBSTRATE.md).
118
+
119
+ ## Development
120
+
121
+ ```bash
122
+ pip install -e "gr2[dev]" # from the repository root
123
+ cd gr2 && pytest # the gr2 suite (tests + overlay tests)
124
+ cd gr2 && ruff check . # lint, same config as CI
125
+ ```
@@ -84,6 +84,7 @@ python_cli/spec_apply.py
84
84
  python_cli/staging_cleanup.py
85
85
  python_cli/syncops.py
86
86
  python_cli/target.py
87
+ python_cli/workspace_guidance.py
87
88
  python_cli/workspace_snapshot.py
88
89
  tests/test_add.py
89
90
  tests/test_agent_manifest_overlay_source.py
@@ -91,6 +92,7 @@ tests/test_app_event_outcomes.py
91
92
  tests/test_apply_convergence.py
92
93
  tests/test_branch.py
93
94
  tests/test_channel_bridge.py
95
+ tests/test_cli_adapter_error_boundary.py
94
96
  tests/test_clone_materialization.py
95
97
  tests/test_commit.py
96
98
  tests/test_config_base_path_guard.py
@@ -103,10 +105,14 @@ tests/test_event_log_integrity.py
103
105
  tests/test_event_outcome_policy.py
104
106
  tests/test_events.py
105
107
  tests/test_events_torn_line.py
108
+ tests/test_exec_apostrophe_and_oserror.py
109
+ tests/test_exec_single_string_and_materialize_plan.py
106
110
  tests/test_execops.py
107
111
  tests/test_failure_event_outcome.py
112
+ tests/test_first_five_minutes.py
108
113
  tests/test_foreground_launch_runtime.py
109
114
  tests/test_git_review.py
115
+ tests/test_gr1_first_run_guidance.py
110
116
  tests/test_gr2_packaging.py
111
117
  tests/test_gr2_review_receipt.py
112
118
  tests/test_grip_cli.py
@@ -117,8 +123,10 @@ tests/test_hook_events.py
117
123
  tests/test_hook_hardening.py
118
124
  tests/test_jsonl_store.py
119
125
  tests/test_lane_create_fork_base_cli.py
126
+ tests/test_lane_create_refuses_own_branch.py
120
127
  tests/test_lane_lease_show_json.py
121
128
  tests/test_lane_lifecycle_safety.py
129
+ tests/test_lane_repo_paths_and_all_skipped.py
122
130
  tests/test_lane_toml_serialization.py
123
131
  tests/test_launch_primitive.py
124
132
  tests/test_m2_overlay_hindsight.py
@@ -129,6 +137,7 @@ tests/test_merge_evidence.py
129
137
  tests/test_merge_parent_verification.py
130
138
  tests/test_migration.py
131
139
  tests/test_migration_overlay_compatibility.py
140
+ tests/test_migration_unadopted_notice.py
132
141
  tests/test_no_bare_python_cli_imports.py
133
142
  tests/test_null_current_lane_refusal.py
134
143
  tests/test_open_gr_enter.py
@@ -165,6 +174,7 @@ tests/test_propagation_daemon.py
165
174
  tests/test_propagation_state_machine.py
166
175
  tests/test_prune.py
167
176
  tests/test_push.py
177
+ tests/test_readme_verbs_exist.py
168
178
  tests/test_repo_status_linked_worktree.py
169
179
  tests/test_repo_status_schema_version.py
170
180
  tests/test_repo_status_symlinked_git_dir.py
@@ -184,6 +194,7 @@ tests/test_review_run_rerun_and_refusal_receipt.py
184
194
  tests/test_review_runners.py
185
195
  tests/test_review_verify_inside.py
186
196
  tests/test_rmtree_or_refuse.py
197
+ tests/test_single_repo_cwd_refusal.py
187
198
  tests/test_snapshot_id_prefix_resolution.py
188
199
  tests/test_spec_apply_event_outcomes.py
189
200
  tests/test_sprint21_sync_platform.py
@@ -208,4 +219,5 @@ tests/test_workspace_convert_clone.py
208
219
  tests/test_workspace_edit_lease_cap.py
209
220
  tests/test_workspace_identity_boundary.py
210
221
  tests/test_workspace_init_from_topology.py
222
+ tests/test_workspace_init_local_repos.py
211
223
  tests/test_workspace_snapshot.py
@@ -195,6 +195,7 @@ class LaneTransitionOutcome:
195
195
  current_lane: str | None
196
196
  state_path: Path
197
197
  status: str = "ok"
198
+ repo_paths: dict[str, str] = dataclasses.field(default_factory=dict)
198
199
 
199
200
  @property
200
201
  def exit_code(self) -> int:
@@ -208,6 +209,7 @@ class LaneTransitionOutcome:
208
209
  "previous_lane": self.previous_lane,
209
210
  "current_lane": self.current_lane,
210
211
  "state_path": str(self.state_path),
212
+ "repo_paths": dict(self.repo_paths),
211
213
  }
212
214
 
213
215
 
@@ -921,6 +923,97 @@ def _validate_bound_worktree(bind_path: Path, workspace_root: Path) -> tuple[str
921
923
  return head, branch
922
924
 
923
925
 
926
+ #: How long the origin may take to report its default branch before the answer is
927
+ #: "cannot tell". Generous for a symref read, and short enough that an origin which
928
+ #: accepts and never replies cannot block `lane create` with no output. Without it,
929
+ #: an offline laptop or a blackholed origin hangs the verb where it used to create a
930
+ #: lane without any network call at all.
931
+ _LS_REMOTE_TIMEOUT_SECONDS = 5.0
932
+
933
+
934
+ def _origin_default_branch(repo_path: Path, url: str | None = None) -> str | None:
935
+ """The repo's integration branch, or None when it cannot be determined.
936
+
937
+ A clone records it as `origin/HEAD`. That symref is ABSENT in a clone made
938
+ before the remote had any refs — which is the normal state of a repo cloned
939
+ empty and pushed later, and it is exactly the state in which a lane created on
940
+ the default branch looks harmless. So when the local record is missing, ask the
941
+ origin itself (`ls-remote --symref`), which is the same remote the lane is about
942
+ to clone from anyway. A remote that cannot be reached gives None: "cannot tell"
943
+ is never allowed to become a refusal. Unreachable includes UNANSWERED — the call
944
+ is bounded, and an expiry is None on the same channel as any other git failure
945
+ rather than an exception the caller has to remember to catch separately.
946
+ """
947
+ proc = gitops.git(repo_path, "rev-parse", "--abbrev-ref", "origin/HEAD")
948
+ if proc.returncode == 0:
949
+ ref = proc.stdout.strip()
950
+ if ref.startswith("origin/"):
951
+ return ref.split("/", 1)[1]
952
+ if not url:
953
+ return None
954
+ remote = gitops.git(
955
+ repo_path, "ls-remote", "--symref", url, "HEAD", timeout=_LS_REMOTE_TIMEOUT_SECONDS
956
+ )
957
+ if remote.returncode != 0:
958
+ # A git that never returned is the same answer as a git that refused:
959
+ # "cannot tell". The bound above is what makes that reachable at all — an
960
+ # origin that accepts and says nothing used to block the verb outright.
961
+ return None
962
+ for line in remote.stdout.splitlines():
963
+ if line.startswith("ref:") and line.rstrip().endswith("HEAD"):
964
+ ref = line.split()[1]
965
+ # Strip the PREFIX, never split on every slash: a default branch whose
966
+ # name contains one (`release/1.x`) came back as its last segment, so the
967
+ # guard compared the requested branch against a string the origin never
968
+ # reported and the no-isolation lane was created in silence.
969
+ if not ref.startswith("refs/heads/"):
970
+ return None
971
+ return ref.removeprefix("refs/heads/")
972
+ return None
973
+
974
+
975
+ def _lane_branch_refusal(workspace_root: Path, spec: dict, repo: str, branch: str) -> str | None:
976
+ """The one sentence that refuses a lane branch with no isolation, or None.
977
+
978
+ A lane is the isolation primitive. A lane whose branch IS the repository's own
979
+ branch has no isolation at all: work committed there and pushed with `gr2 push`
980
+ lands on the branch the lane was supposed to be forked from, and the first
981
+ refusal arrived at `pr create` — after the remote had already been written
982
+ (measured on the released 2.0.0a2: `lane create --branch main`, push exit 0
983
+ "Pushed main", then `pr create` exit 1 "head branch main is the same as base
984
+ branch main"). Nothing refused at the point the choice was made.
985
+
986
+ The comparison is against what is CHECKED OUT here and what the origin says its
987
+ default is. A repo with no local checkout, or one whose origin cannot be
988
+ reached, is skipped rather than refused: this guard may not invent a conflict it
989
+ cannot see.
990
+ """
991
+ entry = next((row for row in spec.get("repos", []) if row.get("name") == repo), None)
992
+ if entry is None:
993
+ return None
994
+ rel = entry.get("path")
995
+ if not rel:
996
+ return None
997
+ checkout = workspace_root / str(rel)
998
+ if not gitops.is_git_repo(checkout):
999
+ return None
1000
+ matched: str | None = None
1001
+ if branch == gitops.current_branch(checkout):
1002
+ matched = f"the branch checked out at {rel}"
1003
+ else:
1004
+ default = _origin_default_branch(checkout, str(entry.get("url") or "") or None)
1005
+ if default and branch == default:
1006
+ matched = f"the repo's integration branch ({default})"
1007
+ if matched is None:
1008
+ return None
1009
+ return (
1010
+ f"lane branch '{branch}' for repo '{repo}' is {matched}: a lane forked from the branch it will "
1011
+ f"land on has no isolation, and a push would go straight to '{branch}'. Pass a lane branch that is "
1012
+ f"neither the repo's checked-out nor its integration branch (e.g. feat/<name>), or use --bind to "
1013
+ f"label an existing worktree."
1014
+ )
1015
+
1016
+
924
1017
  def create_lane(args: argparse.Namespace) -> int:
925
1018
  workspace_root = args.workspace_root.resolve()
926
1019
  validate_lane_path_component(args.owner_unit, "owner_unit")
@@ -961,6 +1054,14 @@ def create_lane(args: argparse.Namespace) -> int:
961
1054
  branch_map = {repos[0]: branch}
962
1055
  else:
963
1056
  branch_map = parse_branch_arg(args.branch, repos)
1057
+ # A materialized lane's branch may not be the repo's own branch: the lane
1058
+ # would have no isolation and its push would land on the integration
1059
+ # branch. Refused HERE, where the choice is made, rather than at the first
1060
+ # `pr create` after the remote had been written.
1061
+ for _repo in repos:
1062
+ refusal = _lane_branch_refusal(workspace_root, spec, _repo, branch_map[_repo])
1063
+ if refusal:
1064
+ raise SystemExit(refusal)
964
1065
 
965
1066
  # Fork base: record what the caller supplies, do not derive.
966
1067
  # Each entry names a repo IN the lane and carries a 40-hex integration-branch tip.
@@ -1038,6 +1139,18 @@ def create_lane(args: argparse.Namespace) -> int:
1038
1139
  (lane_root / "context").mkdir()
1039
1140
  atomic_replace_text(metadata_path, expected)
1040
1141
  print(metadata_path)
1142
+ # The human line names where the actor works, per repo, absolute: a
1143
+ # materialized lane's repos live under its lane tree, a bound lane's one
1144
+ # repo IS the bound worktree. (The stranger's F2 finding: enter/create
1145
+ # used to point at lane.toml only, so the actor never learned where the
1146
+ # lane's repos are.)
1147
+ if lane_kind == "bound" and bound_worktree:
1148
+ for repo in repos:
1149
+ print(f"{repo}: {bound_worktree}")
1150
+ else:
1151
+ lane_root = lane_dir(workspace_root, args.owner_unit, args.lane_name)
1152
+ for repo in repos:
1153
+ print(f"{repo}: {lane_root / 'repos' / repo}")
1041
1154
  return 0
1042
1155
 
1043
1156
 
@@ -1222,6 +1335,17 @@ def enter_lane(args: argparse.Namespace) -> LaneTransitionOutcome:
1222
1335
  previous_lane = current.get("lane_name")
1223
1336
  previous.insert(0, current)
1224
1337
 
1338
+ lane_kind = lane_doc.get("lane_kind", "materialized")
1339
+ if lane_kind == "bound" and lane_doc.get("bound_worktree"):
1340
+ repo_paths = {
1341
+ r: str(lane_doc["bound_worktree"]) for r in lane_doc.get("repos", [])
1342
+ }
1343
+ else:
1344
+ lane_root = lane_dir(workspace_root, args.owner_unit, args.lane_name)
1345
+ repo_paths = {
1346
+ r: str(lane_root / "repos" / r) for r in lane_doc.get("repos", [])
1347
+ }
1348
+
1225
1349
  deduped: list[dict] = []
1226
1350
  seen: set[tuple[str, str]] = set()
1227
1351
  for item in previous:
@@ -1237,6 +1361,7 @@ def enter_lane(args: argparse.Namespace) -> LaneTransitionOutcome:
1237
1361
  "lane_name": args.lane_name,
1238
1362
  "lane_type": lane_doc["lane_type"],
1239
1363
  "repos": lane_doc.get("repos", []),
1364
+ "repo_paths": repo_paths,
1240
1365
  "actor": args.actor,
1241
1366
  "entered_at": now_utc(),
1242
1367
  },
@@ -1270,7 +1395,10 @@ def enter_lane(args: argparse.Namespace) -> LaneTransitionOutcome:
1270
1395
  "timestamp": event["timestamp"],
1271
1396
  },
1272
1397
  )
1273
- return LaneTransitionOutcome("enter", args.owner_unit, previous_lane, args.lane_name, path)
1398
+ return LaneTransitionOutcome(
1399
+ "enter", args.owner_unit, previous_lane, args.lane_name, path,
1400
+ repo_paths=repo_paths,
1401
+ )
1274
1402
 
1275
1403
 
1276
1404
  def exit_lane(args: argparse.Namespace) -> LaneTransitionOutcome: