sase-github 0.2.8__tar.gz → 0.2.10__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 (46) hide show
  1. sase_github-0.2.10/.release-please-manifest.json +3 -0
  2. {sase_github-0.2.8 → sase_github-0.2.10}/CHANGELOG.md +14 -0
  3. {sase_github-0.2.8 → sase_github-0.2.10}/PKG-INFO +2 -2
  4. {sase_github-0.2.8 → sase_github-0.2.10}/pyproject.toml +2 -2
  5. sase_github-0.2.10/src/sase_github/scripts/gh_release.py +27 -0
  6. {sase_github-0.2.8 → sase_github-0.2.10}/src/sase_github/scripts/gh_setup.py +58 -0
  7. {sase_github-0.2.8 → sase_github-0.2.10}/src/sase_github/scripts/new_pr_desc_get_context.py +1 -4
  8. {sase_github-0.2.8 → sase_github-0.2.10}/src/sase_github/workspace_plugin.py +2 -13
  9. {sase_github-0.2.8 → sase_github-0.2.10}/src/sase_github/xprompts/gh.yml +7 -11
  10. {sase_github-0.2.8 → sase_github-0.2.10}/tests/test_gh_workspace_claims.py +97 -7
  11. sase_github-0.2.10/tests/test_new_pr_desc_get_context.py +68 -0
  12. {sase_github-0.2.8 → sase_github-0.2.10}/tests/test_workspace_plugin.py +55 -0
  13. sase_github-0.2.8/.release-please-manifest.json +0 -3
  14. {sase_github-0.2.8 → sase_github-0.2.10}/.github/workflows/ci.yml +0 -0
  15. {sase_github-0.2.8 → sase_github-0.2.10}/.github/workflows/pr-title.yml +0 -0
  16. {sase_github-0.2.8 → sase_github-0.2.10}/.github/workflows/publish.yml +0 -0
  17. {sase_github-0.2.8 → sase_github-0.2.10}/.sase_beads/beads.db +0 -0
  18. {sase_github-0.2.8 → sase_github-0.2.10}/.sase_beads/config.json +0 -0
  19. {sase_github-0.2.8 → sase_github-0.2.10}/.sase_beads/issues.jsonl +0 -0
  20. {sase_github-0.2.8 → sase_github-0.2.10}/CLAUDE.md +0 -0
  21. {sase_github-0.2.8 → sase_github-0.2.10}/Justfile +0 -0
  22. {sase_github-0.2.8 → sase_github-0.2.10}/LICENSE +0 -0
  23. {sase_github-0.2.8 → sase_github-0.2.10}/README.md +0 -0
  24. {sase_github-0.2.8 → sase_github-0.2.10}/docs/architecture.md +0 -0
  25. {sase_github-0.2.8 → sase_github-0.2.10}/docs/configuration.md +0 -0
  26. {sase_github-0.2.8 → sase_github-0.2.10}/docs/xprompts.md +0 -0
  27. {sase_github-0.2.8 → sase_github-0.2.10}/release-please-config.json +0 -0
  28. {sase_github-0.2.8 → sase_github-0.2.10}/src/sase_github/__init__.py +0 -0
  29. {sase_github-0.2.8 → sase_github-0.2.10}/src/sase_github/config.py +0 -0
  30. {sase_github-0.2.8 → sase_github-0.2.10}/src/sase_github/default_config.yml +0 -0
  31. {sase_github-0.2.8 → sase_github-0.2.10}/src/sase_github/errors.py +0 -0
  32. {sase_github-0.2.8 → sase_github-0.2.10}/src/sase_github/plugin.py +0 -0
  33. {sase_github-0.2.8 → sase_github-0.2.10}/src/sase_github/scripts/__init__.py +0 -0
  34. {sase_github-0.2.8 → sase_github-0.2.10}/src/sase_github/task_types.py +0 -0
  35. {sase_github-0.2.8 → sase_github-0.2.10}/src/sase_github/xprompts/new_pr_desc.yml +0 -0
  36. {sase_github-0.2.8 → sase_github-0.2.10}/src/sase_github/xprompts/pr_diff.yml +0 -0
  37. {sase_github-0.2.8 → sase_github-0.2.10}/src/sase_github/xprompts/prdd.yml +0 -0
  38. {sase_github-0.2.8 → sase_github-0.2.10}/tests/__init__.py +0 -0
  39. {sase_github-0.2.8 → sase_github-0.2.10}/tests/test_ci_install_contract.py +0 -0
  40. {sase_github-0.2.8 → sase_github-0.2.10}/tests/test_config.py +0 -0
  41. {sase_github-0.2.8 → sase_github-0.2.10}/tests/test_github_issues.py +0 -0
  42. {sase_github-0.2.8 → sase_github-0.2.10}/tests/test_github_plugin.py +0 -0
  43. {sase_github-0.2.8 → sase_github-0.2.10}/tests/test_github_pull_requests.py +0 -0
  44. {sase_github-0.2.8 → sase_github-0.2.10}/tests/test_submit_with_recorded_pr.py +0 -0
  45. {sase_github-0.2.8 → sase_github-0.2.10}/tests/test_task_types.py +0 -0
  46. {sase_github-0.2.8 → sase_github-0.2.10}/uv.lock +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.2.10"
3
+ }
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.10](https://github.com/sase-org/sase-github/compare/v0.2.9...v0.2.10) (2026-09-08)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **gh:** reject non-github setup workspaces ([f032e91](https://github.com/sase-org/sase-github/commit/f032e91b0b10a5a71af913b7d2ad424e1700b93c))
9
+
10
+ ## [0.2.9](https://github.com/sase-org/sase-github/compare/v0.2.8...v0.2.9) (2026-08-29)
11
+
12
+
13
+ ### Features
14
+
15
+ * **workspace:** identity-check #gh release and skip on handoff ([2571c9d](https://github.com/sase-org/sase-github/commit/2571c9d7466d4c6020a87b6ee86068c12170cdab))
16
+
3
17
  ## [0.2.8](https://github.com/sase-org/sase-github/compare/v0.2.7...v0.2.8) (2026-08-29)
4
18
 
5
19
 
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sase-github
3
- Version: 0.2.8
3
+ Version: 0.2.10
4
4
  Summary: GitHub VCS plugin for sase
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
7
7
  Requires-Python: >=3.12
8
- Requires-Dist: sase>=0.11.0
8
+ Requires-Dist: sase>=0.17.0
9
9
  Provides-Extra: dev
10
10
  Requires-Dist: mypy; extra == 'dev'
11
11
  Requires-Dist: pytest; extra == 'dev'
@@ -4,12 +4,12 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sase-github"
7
- version = "0.2.8"
7
+ version = "0.2.10"
8
8
  description = "GitHub VCS plugin for sase"
9
9
  requires-python = ">=3.12"
10
10
  license = "MIT"
11
11
  license-files = ["LICENSE"]
12
- dependencies = ["sase>=0.11.0"]
12
+ dependencies = ["sase>=0.17.0"]
13
13
 
14
14
  [project.optional-dependencies]
15
15
  dev = [
@@ -0,0 +1,27 @@
1
+ """Release step for the #gh xprompt workflow."""
2
+
3
+ from sase.workspace_provider.vcs_release import VcsReleaseResult, release_vcs_workspace
4
+
5
+ _CALLER_TAG = "gh-release"
6
+
7
+
8
+ def main(
9
+ *,
10
+ project_file: str,
11
+ workspace_num: int,
12
+ workspace_dir: str,
13
+ workflow_name: str,
14
+ cl_name: str | None,
15
+ ) -> None:
16
+ """Identity-checked, handoff-aware release of the #gh workspace claim."""
17
+ result: VcsReleaseResult = release_vcs_workspace(
18
+ project_file=project_file,
19
+ workspace_num=workspace_num,
20
+ workspace_dir=workspace_dir,
21
+ workflow_name=workflow_name,
22
+ cl_name=cl_name,
23
+ caller_tag=_CALLER_TAG,
24
+ )
25
+ print(f"released={'true' if result.released else 'false'}")
26
+ if result.skip_reason:
27
+ print(f"skip_reason={result.skip_reason}")
@@ -1,6 +1,7 @@
1
1
  """Setup step for the #gh xprompt workflow."""
2
2
 
3
3
  import os
4
+ import subprocess
4
5
  import sys
5
6
 
6
7
  from sase.core.occupancy_guard import OccupancyCaller, ensure_workspace_not_occupied
@@ -84,6 +85,22 @@ def main(
84
85
  pinned=not release,
85
86
  )
86
87
 
88
+ try:
89
+ _assert_github_vcs_provider(
90
+ workspace_dir=workspace_dir,
91
+ primary_workspace_dir=resolved.primary_workspace_dir,
92
+ )
93
+ except Exception:
94
+ if not pre_allocated:
95
+ release_workspace(
96
+ project_file,
97
+ workspace_num,
98
+ workflow_name,
99
+ cl_name,
100
+ caller_tag=_CALLER_TAG,
101
+ )
102
+ raise
103
+
87
104
  # Refuse to hand this checkout to `prepare`/`checkout` if another live
88
105
  # agent still occupies it. Runs on the pre_allocated branch too: the
89
106
  # launcher already wrote its own occupant record, and the conflict
@@ -254,6 +271,47 @@ def _materialize_or_release(
254
271
  return workspace_dir
255
272
 
256
273
 
274
+ def _assert_github_vcs_provider(
275
+ *,
276
+ workspace_dir: str,
277
+ primary_workspace_dir: str,
278
+ ) -> None:
279
+ provider_name = _detect_vcs_provider_name(workspace_dir)
280
+ if provider_name == "github":
281
+ return
282
+ actual_origin = _origin_url(workspace_dir)
283
+ expected_origin = _origin_url(primary_workspace_dir)
284
+ raise RuntimeError(
285
+ "GitHub workflow refused workspace because its VCS provider resolved "
286
+ f"to {provider_name or 'unknown'!r}, not 'github'. "
287
+ f"workspace origin: {actual_origin or '<unreadable>'}; "
288
+ f"expected GitHub origin: {expected_origin or '<unreadable>'}"
289
+ )
290
+
291
+
292
+ def _detect_vcs_provider_name(workspace_dir: str) -> str | None:
293
+ from sase.vcs_provider import detect_vcs
294
+
295
+ return detect_vcs(workspace_dir)
296
+
297
+
298
+ def _origin_url(workspace_dir: str) -> str | None:
299
+ try:
300
+ result = subprocess.run(
301
+ ["git", "config", "--get", "remote.origin.url"],
302
+ cwd=workspace_dir,
303
+ capture_output=True,
304
+ text=True,
305
+ check=False,
306
+ timeout=10,
307
+ )
308
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
309
+ return None
310
+ if result.returncode != 0:
311
+ return None
312
+ return result.stdout.strip() or None
313
+
314
+
257
315
  def _describe_workspace_occupant(project_file: str, workspace_num: int) -> str | None:
258
316
  occupants = [
259
317
  claim
@@ -7,10 +7,7 @@ from pathlib import Path
7
7
 
8
8
  from sase.workspace_provider.utils import get_default_branch, parse_workspace_dir
9
9
 
10
- try:
11
- from sase.ace.patch import find_all_patches
12
- except ImportError: # Older supported SASE releases expose only ChangeSpec names.
13
- from sase.ace.changespec import find_all_changespecs as find_all_patches
10
+ from sase.ace.patch import find_all_patches
14
11
 
15
12
  try:
16
13
  from sase.core.paths import ( # type: ignore[attr-defined]
@@ -17,19 +17,8 @@ from collections.abc import Mapping, Sequence
17
17
  from pathlib import Path
18
18
  from typing import TYPE_CHECKING, Any, Literal
19
19
 
20
- try:
21
- from sase.ace.patch import Patch, find_all_patches
22
- from sase.ace.patch.project_spec_path import preferred_project_spec_path
23
- except ImportError: # Older supported SASE releases expose only ChangeSpec names.
24
- from sase.ace.changespec import ( # type: ignore[no-redef]
25
- # Legacy import names are adapted to canonical Patch locals.
26
- ChangeSpec as Patch,
27
- # Legacy import names are adapted to canonical Patch locals.
28
- find_all_changespecs as find_all_patches,
29
- )
30
- from sase.ace.changespec.project_spec_path import ( # type: ignore[no-redef]
31
- preferred_project_spec_path,
32
- )
20
+ from sase.ace.patch import Patch, find_all_patches
21
+ from sase.ace.patch.project_spec_path import preferred_project_spec_path
33
22
  from sase.workspace_provider import (
34
23
  ExternalRepoCloneResult,
35
24
  ResolvedRef,
@@ -1,5 +1,4 @@
1
1
  description: Check out a GitHub ref in an isolated workspace and show resulting changes.
2
- wraps_all: true
3
2
  tags: vcs, rollover
4
3
 
5
4
  input:
@@ -120,18 +119,15 @@ steps:
120
119
  finally: true
121
120
  if: "{{ setup.should_release }}"
122
121
  python: |
123
- from sase.running_field import release_workspace
124
- from sase.workspace_provider.occupant import clear_occupant_record
122
+ from sase_github.scripts.gh_release import main
125
123
 
126
- release_workspace(
127
- {{ setup.project_file | tojson }},
128
- {{ setup.workspace_num }},
129
- {{ setup.workflow_name | tojson }},
130
- {{ gh_ref | tojson }} if "/" not in {{ gh_ref | tojson }} else None,
131
- caller_tag="gh-release",
124
+ main(
125
+ project_file={{ setup.project_file | tojson }},
126
+ workspace_num={{ setup.workspace_num }},
127
+ workspace_dir={{ setup.workspace_dir | tojson }},
128
+ workflow_name={{ setup.workflow_name | tojson }},
129
+ cl_name={{ gh_ref | tojson }} if "/" not in {{ gh_ref | tojson }} else None,
132
130
  )
133
- clear_occupant_record({{ setup.workspace_dir | tojson }})
134
- print("released=true")
135
131
  output: { released: bool }
136
132
 
137
133
  - name: diff
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
+ from collections.abc import Iterator
6
7
  from pathlib import Path
7
8
  from types import SimpleNamespace
8
9
  from unittest.mock import MagicMock, patch
@@ -23,12 +24,18 @@ from sase.workspace_provider.occupant import (
23
24
  write_occupant_record,
24
25
  )
25
26
 
26
- from sase_github.scripts import gh_setup
27
+ from sase_github.scripts import gh_release, gh_setup
27
28
  from sase_github.workspace_plugin import GitHubWorkspacePlugin
28
29
 
29
30
  ROOT = Path(__file__).resolve().parents[1]
30
31
 
31
32
 
33
+ @pytest.fixture(autouse=True)
34
+ def _github_provider_guard_passes() -> Iterator[None]:
35
+ with patch("sase_github.scripts.gh_setup._assert_github_vcs_provider"):
36
+ yield
37
+
38
+
32
39
  def _write_project_file(
33
40
  tmp_path: Path, running_claims: list[WorkspaceClaim] | None = None
34
41
  ) -> str:
@@ -939,16 +946,99 @@ class TestWsSubmitAtomicClaim:
939
946
  )
940
947
 
941
948
 
942
- def test_gh_release_step_tags_caller() -> None:
949
+ def test_gh_release_step_delegates_to_gh_release() -> None:
943
950
  text = (ROOT / "src" / "sase_github" / "xprompts" / "gh.yml").read_text(
944
951
  encoding="utf-8"
945
952
  )
946
- assert 'caller_tag="gh-release"' in text
953
+ assert "from sase_github.scripts.gh_release import main" in text
954
+ assert "workspace_dir={{ setup.workspace_dir | tojson }}" in text
955
+ assert "release_workspace(" not in text
956
+ assert "clear_occupant_record(" not in text
947
957
 
948
958
 
949
- def test_gh_release_step_clears_occupant_record() -> None:
950
- text = (ROOT / "src" / "sase_github" / "xprompts" / "gh.yml").read_text(
959
+ def test_gh_release_caller_tag() -> None:
960
+ source = (ROOT / "src" / "sase_github" / "scripts" / "gh_release.py").read_text(
951
961
  encoding="utf-8"
952
962
  )
953
- assert "clear_occupant_record(" in text
954
- assert "setup.workspace_dir" in text.split("clear_occupant_record(", 1)[1]
963
+ assert '_CALLER_TAG = "gh-release"' in source
964
+
965
+
966
+ def test_gh_release_step_releases_owned_claim_and_occupant(
967
+ tmp_path: Path, capsys: pytest.CaptureFixture[str]
968
+ ) -> None:
969
+ runner_pid = os.getppid()
970
+ project_file = _write_project_file(
971
+ tmp_path,
972
+ running_claims=[WorkspaceClaim(23, "gh-acme/widget", None, pid=runner_pid)],
973
+ )
974
+ workspace_dir = tmp_path / "widget_23"
975
+ workspace_dir.mkdir()
976
+ write_occupant_record(
977
+ str(workspace_dir),
978
+ new_occupant_record(
979
+ pid=runner_pid,
980
+ workflow="gh-acme/widget",
981
+ project="gh_acme__widget",
982
+ workspace_num=23,
983
+ ),
984
+ )
985
+
986
+ with _isolate_ledger(tmp_path):
987
+ gh_release.main(
988
+ project_file=project_file,
989
+ workspace_num=23,
990
+ workspace_dir=str(workspace_dir),
991
+ workflow_name="gh-acme/widget",
992
+ cl_name=None,
993
+ )
994
+
995
+ out = capsys.readouterr().out
996
+ assert "released=true" in out
997
+ assert get_claimed_workspaces(project_file) == []
998
+ assert read_occupant_record(str(workspace_dir)) is None
999
+
1000
+
1001
+ def test_gh_release_step_skips_on_handoff(
1002
+ tmp_path: Path, capsys: pytest.CaptureFixture[str]
1003
+ ) -> None:
1004
+ runner_pid = os.getppid()
1005
+ project_file = _write_project_file(
1006
+ tmp_path,
1007
+ running_claims=[WorkspaceClaim(23, "gh-acme/widget", None, pid=runner_pid)],
1008
+ )
1009
+ workspace_dir = tmp_path / "widget_23"
1010
+ workspace_dir.mkdir()
1011
+ write_occupant_record(
1012
+ str(workspace_dir),
1013
+ new_occupant_record(
1014
+ pid=runner_pid,
1015
+ workflow="gh-acme/widget",
1016
+ project="gh_acme__widget",
1017
+ workspace_num=23,
1018
+ ),
1019
+ )
1020
+ artifacts = tmp_path / "artifacts"
1021
+ artifacts.mkdir()
1022
+ (artifacts / ".sase_monitor_pending").write_text("{}", encoding="utf-8")
1023
+
1024
+ with (
1025
+ _isolate_ledger(tmp_path),
1026
+ patch.dict(os.environ, {"SASE_ARTIFACTS_DIR": str(artifacts)}),
1027
+ ):
1028
+ gh_release.main(
1029
+ project_file=project_file,
1030
+ workspace_num=23,
1031
+ workspace_dir=str(workspace_dir),
1032
+ workflow_name="gh-acme/widget",
1033
+ cl_name=None,
1034
+ )
1035
+
1036
+ out = capsys.readouterr().out
1037
+ assert "released=false" in out
1038
+ assert "skip_reason=handoff" in out
1039
+ assert [claim.workspace_num for claim in get_claimed_workspaces(project_file)] == [
1040
+ 23
1041
+ ]
1042
+ occupant = read_occupant_record(str(workspace_dir))
1043
+ assert occupant is not None
1044
+ assert occupant.pid == runner_pid
@@ -0,0 +1,68 @@
1
+ """Tests for the #new_pr_desc xprompt's get-context script."""
2
+
3
+ from pathlib import Path
4
+ from types import SimpleNamespace
5
+ from unittest.mock import patch
6
+
7
+ import pytest
8
+
9
+ from sase_github.scripts import new_pr_desc_get_context
10
+
11
+
12
+ def test_main_reports_context_for_found_patch(
13
+ tmp_path: Path,
14
+ capsys: pytest.CaptureFixture[str],
15
+ ) -> None:
16
+ """The script resolves the Patch through the canonical ``sase.ace.patch`` API."""
17
+ workspace_dir = tmp_path / "workspace"
18
+ workspace_dir.mkdir()
19
+ diff_dir = tmp_path / "gh-diffs"
20
+ diff_dir.mkdir()
21
+ entry = SimpleNamespace(
22
+ name="feature",
23
+ file_path=str(workspace_dir / "feature.sase"),
24
+ description="Add feature",
25
+ )
26
+
27
+ with (
28
+ patch(
29
+ "sase_github.scripts.new_pr_desc_get_context.find_all_patches",
30
+ return_value=[entry],
31
+ ),
32
+ patch(
33
+ "sase_github.scripts.new_pr_desc_get_context.parse_workspace_dir",
34
+ return_value=str(workspace_dir),
35
+ ),
36
+ patch(
37
+ "sase_github.scripts.new_pr_desc_get_context.get_default_branch",
38
+ return_value="origin/main",
39
+ ),
40
+ patch(
41
+ "sase_github.scripts.new_pr_desc_get_context.subprocess.run",
42
+ return_value=SimpleNamespace(returncode=0, stdout=""),
43
+ ),
44
+ patch(
45
+ "sase_github.scripts.new_pr_desc_get_context.get_sase_managed_tmpdir",
46
+ return_value=str(diff_dir),
47
+ ),
48
+ ):
49
+ new_pr_desc_get_context.main(name="feature")
50
+
51
+ out = capsys.readouterr().out
52
+ assert "error=\n" in out
53
+ assert "description=Add feature\n" in out
54
+ assert "default_branch=main\n" in out
55
+ assert "branch_name=feature\n" in out
56
+
57
+
58
+ def test_main_reports_error_for_missing_patch(
59
+ capsys: pytest.CaptureFixture[str],
60
+ ) -> None:
61
+ with patch(
62
+ "sase_github.scripts.new_pr_desc_get_context.find_all_patches",
63
+ return_value=[],
64
+ ):
65
+ new_pr_desc_get_context.main(name="missing")
66
+
67
+ out = capsys.readouterr().out
68
+ assert "error=Patch 'missing' not found\n" in out
@@ -2096,6 +2096,7 @@ class TestGhSetup:
2096
2096
  "sase_github.scripts.gh_setup.materialize_sdd_store",
2097
2097
  side_effect=materialize,
2098
2098
  ) as materialize_mock,
2099
+ patch("sase_github.scripts.gh_setup._assert_github_vcs_provider"),
2099
2100
  ):
2100
2101
  gh_setup.main(gh_ref="acme/widget", n=None, release=False)
2101
2102
 
@@ -2142,6 +2143,60 @@ class TestGhSetup:
2142
2143
  caller_tag="gh-setup",
2143
2144
  )
2144
2145
 
2146
+ def test_non_github_workspace_provider_releases_claimed_slot(self) -> None:
2147
+ from sase_github.scripts import gh_setup
2148
+
2149
+ resolved = ResolvedRef(
2150
+ project_file="/tmp/gh_acme__widget.sase",
2151
+ project_name="gh_acme__widget",
2152
+ primary_workspace_dir="/work/widget/",
2153
+ checkout_target="origin/main",
2154
+ canonical_ref="gh_acme__widget",
2155
+ )
2156
+
2157
+ with (
2158
+ patch("sase_github.scripts.gh_setup.resolve_ref", return_value=resolved),
2159
+ patch.dict(os.environ, {"SASE_GH_PRE_ALLOCATED": "0"}),
2160
+ patch(
2161
+ "sase_github.scripts.gh_setup.ensure_workspace_checkout",
2162
+ return_value="/work/widget_7/",
2163
+ ),
2164
+ patch(
2165
+ "sase_github.scripts.gh_setup.claim_next_axe_workspace",
2166
+ return_value=7,
2167
+ ),
2168
+ patch(
2169
+ "sase_github.scripts.gh_setup.materialize_sdd_store",
2170
+ return_value=None,
2171
+ ),
2172
+ patch(
2173
+ "sase_github.scripts.gh_setup._detect_vcs_provider_name",
2174
+ return_value="bare_git",
2175
+ ),
2176
+ patch(
2177
+ "sase_github.scripts.gh_setup._origin_url",
2178
+ side_effect=lambda path: {
2179
+ "/work/widget_7/": "/work/widget/",
2180
+ "/work/widget/": "git@github.com:acme/widget.git",
2181
+ }[path],
2182
+ ),
2183
+ patch("sase_github.scripts.gh_setup.release_workspace") as release,
2184
+ pytest.raises(RuntimeError) as exc_info,
2185
+ ):
2186
+ gh_setup.main(gh_ref="acme/widget", n=None, release=False)
2187
+
2188
+ message = str(exc_info.value)
2189
+ assert "VCS provider resolved to 'bare_git'" in message
2190
+ assert "workspace origin: /work/widget/" in message
2191
+ assert "expected GitHub origin: git@github.com:acme/widget.git" in message
2192
+ release.assert_called_once_with(
2193
+ "/tmp/gh_acme__widget.sase",
2194
+ 7,
2195
+ "gh-acme/widget",
2196
+ None,
2197
+ caller_tag="gh-setup",
2198
+ )
2199
+
2145
2200
 
2146
2201
  # ── detect_workflow_type (via plugin) ────────────────────────────────
2147
2202
 
@@ -1,3 +0,0 @@
1
- {
2
- ".": "0.2.8"
3
- }
File without changes
File without changes
File without changes
File without changes
File without changes