sase-github 0.2.7__tar.gz → 0.2.9__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 (45) hide show
  1. sase_github-0.2.9/.release-please-manifest.json +3 -0
  2. {sase_github-0.2.7 → sase_github-0.2.9}/CHANGELOG.md +14 -0
  3. {sase_github-0.2.7 → sase_github-0.2.9}/PKG-INFO +1 -1
  4. {sase_github-0.2.7 → sase_github-0.2.9}/pyproject.toml +1 -1
  5. sase_github-0.2.9/src/sase_github/scripts/gh_release.py +27 -0
  6. {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/scripts/gh_setup.py +7 -1
  7. {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/xprompts/gh.yml +8 -10
  8. {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_gh_workspace_claims.py +96 -9
  9. sase_github-0.2.7/.release-please-manifest.json +0 -3
  10. {sase_github-0.2.7 → sase_github-0.2.9}/.github/workflows/ci.yml +0 -0
  11. {sase_github-0.2.7 → sase_github-0.2.9}/.github/workflows/pr-title.yml +0 -0
  12. {sase_github-0.2.7 → sase_github-0.2.9}/.github/workflows/publish.yml +0 -0
  13. {sase_github-0.2.7 → sase_github-0.2.9}/.sase_beads/beads.db +0 -0
  14. {sase_github-0.2.7 → sase_github-0.2.9}/.sase_beads/config.json +0 -0
  15. {sase_github-0.2.7 → sase_github-0.2.9}/.sase_beads/issues.jsonl +0 -0
  16. {sase_github-0.2.7 → sase_github-0.2.9}/CLAUDE.md +0 -0
  17. {sase_github-0.2.7 → sase_github-0.2.9}/Justfile +0 -0
  18. {sase_github-0.2.7 → sase_github-0.2.9}/LICENSE +0 -0
  19. {sase_github-0.2.7 → sase_github-0.2.9}/README.md +0 -0
  20. {sase_github-0.2.7 → sase_github-0.2.9}/docs/architecture.md +0 -0
  21. {sase_github-0.2.7 → sase_github-0.2.9}/docs/configuration.md +0 -0
  22. {sase_github-0.2.7 → sase_github-0.2.9}/docs/xprompts.md +0 -0
  23. {sase_github-0.2.7 → sase_github-0.2.9}/release-please-config.json +0 -0
  24. {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/__init__.py +0 -0
  25. {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/config.py +0 -0
  26. {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/default_config.yml +0 -0
  27. {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/errors.py +0 -0
  28. {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/plugin.py +0 -0
  29. {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/scripts/__init__.py +0 -0
  30. {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/scripts/new_pr_desc_get_context.py +0 -0
  31. {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/task_types.py +0 -0
  32. {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/workspace_plugin.py +0 -0
  33. {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/xprompts/new_pr_desc.yml +0 -0
  34. {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/xprompts/pr_diff.yml +0 -0
  35. {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/xprompts/prdd.yml +0 -0
  36. {sase_github-0.2.7 → sase_github-0.2.9}/tests/__init__.py +0 -0
  37. {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_ci_install_contract.py +0 -0
  38. {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_config.py +0 -0
  39. {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_github_issues.py +0 -0
  40. {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_github_plugin.py +0 -0
  41. {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_github_pull_requests.py +0 -0
  42. {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_submit_with_recorded_pr.py +0 -0
  43. {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_task_types.py +0 -0
  44. {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_workspace_plugin.py +0 -0
  45. {sase_github-0.2.7 → sase_github-0.2.9}/uv.lock +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.2.9"
3
+ }
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.9](https://github.com/sase-org/sase-github/compare/v0.2.8...v0.2.9) (2026-08-29)
4
+
5
+
6
+ ### Features
7
+
8
+ * **workspace:** identity-check #gh release and skip on handoff ([2571c9d](https://github.com/sase-org/sase-github/commit/2571c9d7466d4c6020a87b6ee86068c12170cdab))
9
+
10
+ ## [0.2.8](https://github.com/sase-org/sase-github/compare/v0.2.7...v0.2.8) (2026-08-29)
11
+
12
+
13
+ ### Features
14
+
15
+ * **gh:** mark runner-bound workspace allocations ([f4663ae](https://github.com/sase-org/sase-github/commit/f4663ae526929b23378372005662c20b495bd0f7))
16
+
3
17
  ## [0.2.7](https://github.com/sase-org/sase-github/compare/v0.2.6...v0.2.7) (2026-08-28)
4
18
 
5
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sase-github
3
- Version: 0.2.7
3
+ Version: 0.2.9
4
4
  Summary: GitHub VCS plugin for sase
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sase-github"
7
- version = "0.2.7"
7
+ version = "0.2.9"
8
8
  description = "GitHub VCS plugin for sase"
9
9
  requires-python = ">=3.12"
10
10
  license = "MIT"
@@ -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}")
@@ -11,6 +11,7 @@ from sase.running_field import (
11
11
  find_runner_numbered_workspace,
12
12
  get_claimed_workspaces,
13
13
  release_workspace,
14
+ runner_has_placeholder_workspace,
14
15
  )
15
16
  from sase.sdd.store import materialize_sdd_store
16
17
  from sase.workspace_provider import resolve_ref
@@ -44,6 +45,7 @@ def main(
44
45
  # then incorrectly remove the RUNNING entry while the real workflow
45
46
  # (the parent process) is still alive.
46
47
  pid = os.getppid()
48
+ runner_bound_workspace = False
47
49
 
48
50
  # Skip claiming when pre-allocated — the launcher already claimed the workspace
49
51
  pre_allocated = os.environ.get("SASE_GH_PRE_ALLOCATED") == "1"
@@ -70,6 +72,9 @@ def main(
70
72
  workspace_num, workspace_dir = adopted
71
73
  pre_allocated = True
72
74
  else:
75
+ runner_bound_workspace = runner_has_placeholder_workspace(
76
+ project_file, pid=pid
77
+ )
73
78
  workspace_num, workspace_dir = _claim_next_workspace(
74
79
  project_file=project_file,
75
80
  primary_workspace_dir=resolved.primary_workspace_dir,
@@ -131,8 +136,9 @@ def main(
131
136
  print(f"checkout_target={resolved.checkout_target}")
132
137
  print(f"primary_workspace_dir={resolved.primary_workspace_dir}")
133
138
  # Don't release pre-allocated workspaces — the launcher handles that
134
- should_release = release and not pre_allocated
139
+ should_release = release and not pre_allocated and not runner_bound_workspace
135
140
  print(f"should_release={'true' if should_release else 'false'}")
141
+ print(f"runner_bound_workspace={'true' if runner_bound_workspace else 'false'}")
136
142
  print(f"_chdir={workspace_dir}")
137
143
  print(f"meta_workspace={workspace_num}")
138
144
  print(f"workflow_name={workflow_name}")
@@ -37,6 +37,7 @@ steps:
37
37
  checkout_target: word
38
38
  primary_workspace_dir: path
39
39
  should_release: bool
40
+ runner_bound_workspace: bool
40
41
  workflow_name: word
41
42
 
42
43
  - name: prepare
@@ -119,18 +120,15 @@ steps:
119
120
  finally: true
120
121
  if: "{{ setup.should_release }}"
121
122
  python: |
122
- from sase.running_field import release_workspace
123
- from sase.workspace_provider.occupant import clear_occupant_record
123
+ from sase_github.scripts.gh_release import main
124
124
 
125
- release_workspace(
126
- {{ setup.project_file | tojson }},
127
- {{ setup.workspace_num }},
128
- {{ setup.workflow_name | tojson }},
129
- {{ gh_ref | tojson }} if "/" not in {{ gh_ref | tojson }} else None,
130
- caller_tag="gh-release",
125
+ main(
126
+ project_file={{ setup.project_file | tojson }},
127
+ workspace_num={{ setup.workspace_num }},
128
+ workspace_dir={{ setup.workspace_dir | tojson }},
129
+ workflow_name={{ setup.workflow_name | tojson }},
130
+ cl_name={{ gh_ref | tojson }} if "/" not in {{ gh_ref | tojson }} else None,
131
131
  )
132
- clear_occupant_record({{ setup.workspace_dir | tojson }})
133
- print("released=true")
134
132
  output: { released: bool }
135
133
 
136
134
  - name: diff
@@ -23,7 +23,7 @@ from sase.workspace_provider.occupant import (
23
23
  write_occupant_record,
24
24
  )
25
25
 
26
- from sase_github.scripts import gh_setup
26
+ from sase_github.scripts import gh_release, gh_setup
27
27
  from sase_github.workspace_plugin import GitHubWorkspacePlugin
28
28
 
29
29
  ROOT = Path(__file__).resolve().parents[1]
@@ -608,7 +608,7 @@ class TestGhSetupAdoptsRunnerWorkspace:
608
608
  assert claims[0].pid == os.getppid()
609
609
  assert read_occupant_record(str(workspace_dir)) is None
610
610
 
611
- def test_does_not_adopt_when_parent_only_holds_placeholder(
611
+ def test_parent_placeholder_allocation_is_runner_bound(
612
612
  self, tmp_path: Path, capsys: pytest.CaptureFixture[str]
613
613
  ) -> None:
614
614
  placeholder = WorkspaceClaim(
@@ -635,9 +635,13 @@ class TestGhSetupAdoptsRunnerWorkspace:
635
635
  claim.workspace_num for claim in get_claimed_workspaces(project_file)
636
636
  }
637
637
  assert claimed_nums == {0, 10}
638
+ claims = get_claimed_workspaces(project_file)
639
+ allocated = next(claim for claim in claims if claim.workspace_num == 10)
640
+ assert allocated.pid == os.getppid()
638
641
  out = capsys.readouterr().out
639
642
  assert "workspace_num=10" in out
640
- assert "should_release=true" in out
643
+ assert "should_release=false" in out
644
+ assert "runner_bound_workspace=true" in out
641
645
 
642
646
  def test_pinned_n_does_not_adopt_parent_claim(
643
647
  self, tmp_path: Path, capsys: pytest.CaptureFixture[str]
@@ -935,16 +939,99 @@ class TestWsSubmitAtomicClaim:
935
939
  )
936
940
 
937
941
 
938
- def test_gh_release_step_tags_caller() -> None:
942
+ def test_gh_release_step_delegates_to_gh_release() -> None:
939
943
  text = (ROOT / "src" / "sase_github" / "xprompts" / "gh.yml").read_text(
940
944
  encoding="utf-8"
941
945
  )
942
- assert 'caller_tag="gh-release"' in text
946
+ assert "from sase_github.scripts.gh_release import main" in text
947
+ assert "workspace_dir={{ setup.workspace_dir | tojson }}" in text
948
+ assert "release_workspace(" not in text
949
+ assert "clear_occupant_record(" not in text
943
950
 
944
951
 
945
- def test_gh_release_step_clears_occupant_record() -> None:
946
- text = (ROOT / "src" / "sase_github" / "xprompts" / "gh.yml").read_text(
952
+ def test_gh_release_caller_tag() -> None:
953
+ source = (ROOT / "src" / "sase_github" / "scripts" / "gh_release.py").read_text(
947
954
  encoding="utf-8"
948
955
  )
949
- assert "clear_occupant_record(" in text
950
- assert "setup.workspace_dir" in text.split("clear_occupant_record(", 1)[1]
956
+ assert '_CALLER_TAG = "gh-release"' in source
957
+
958
+
959
+ def test_gh_release_step_releases_owned_claim_and_occupant(
960
+ tmp_path: Path, capsys: pytest.CaptureFixture[str]
961
+ ) -> None:
962
+ runner_pid = os.getppid()
963
+ project_file = _write_project_file(
964
+ tmp_path,
965
+ running_claims=[WorkspaceClaim(23, "gh-acme/widget", None, pid=runner_pid)],
966
+ )
967
+ workspace_dir = tmp_path / "widget_23"
968
+ workspace_dir.mkdir()
969
+ write_occupant_record(
970
+ str(workspace_dir),
971
+ new_occupant_record(
972
+ pid=runner_pid,
973
+ workflow="gh-acme/widget",
974
+ project="gh_acme__widget",
975
+ workspace_num=23,
976
+ ),
977
+ )
978
+
979
+ with _isolate_ledger(tmp_path):
980
+ gh_release.main(
981
+ project_file=project_file,
982
+ workspace_num=23,
983
+ workspace_dir=str(workspace_dir),
984
+ workflow_name="gh-acme/widget",
985
+ cl_name=None,
986
+ )
987
+
988
+ out = capsys.readouterr().out
989
+ assert "released=true" in out
990
+ assert get_claimed_workspaces(project_file) == []
991
+ assert read_occupant_record(str(workspace_dir)) is None
992
+
993
+
994
+ def test_gh_release_step_skips_on_handoff(
995
+ tmp_path: Path, capsys: pytest.CaptureFixture[str]
996
+ ) -> None:
997
+ runner_pid = os.getppid()
998
+ project_file = _write_project_file(
999
+ tmp_path,
1000
+ running_claims=[WorkspaceClaim(23, "gh-acme/widget", None, pid=runner_pid)],
1001
+ )
1002
+ workspace_dir = tmp_path / "widget_23"
1003
+ workspace_dir.mkdir()
1004
+ write_occupant_record(
1005
+ str(workspace_dir),
1006
+ new_occupant_record(
1007
+ pid=runner_pid,
1008
+ workflow="gh-acme/widget",
1009
+ project="gh_acme__widget",
1010
+ workspace_num=23,
1011
+ ),
1012
+ )
1013
+ artifacts = tmp_path / "artifacts"
1014
+ artifacts.mkdir()
1015
+ (artifacts / ".sase_monitor_pending").write_text("{}", encoding="utf-8")
1016
+
1017
+ with (
1018
+ _isolate_ledger(tmp_path),
1019
+ patch.dict(os.environ, {"SASE_ARTIFACTS_DIR": str(artifacts)}),
1020
+ ):
1021
+ gh_release.main(
1022
+ project_file=project_file,
1023
+ workspace_num=23,
1024
+ workspace_dir=str(workspace_dir),
1025
+ workflow_name="gh-acme/widget",
1026
+ cl_name=None,
1027
+ )
1028
+
1029
+ out = capsys.readouterr().out
1030
+ assert "released=false" in out
1031
+ assert "skip_reason=handoff" in out
1032
+ assert [claim.workspace_num for claim in get_claimed_workspaces(project_file)] == [
1033
+ 23
1034
+ ]
1035
+ occupant = read_occupant_record(str(workspace_dir))
1036
+ assert occupant is not None
1037
+ assert occupant.pid == runner_pid
@@ -1,3 +0,0 @@
1
- {
2
- ".": "0.2.7"
3
- }
File without changes
File without changes
File without changes
File without changes
File without changes