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.
- sase_github-0.2.9/.release-please-manifest.json +3 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/CHANGELOG.md +14 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/PKG-INFO +1 -1
- {sase_github-0.2.7 → sase_github-0.2.9}/pyproject.toml +1 -1
- sase_github-0.2.9/src/sase_github/scripts/gh_release.py +27 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/scripts/gh_setup.py +7 -1
- {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/xprompts/gh.yml +8 -10
- {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_gh_workspace_claims.py +96 -9
- sase_github-0.2.7/.release-please-manifest.json +0 -3
- {sase_github-0.2.7 → sase_github-0.2.9}/.github/workflows/ci.yml +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/.github/workflows/pr-title.yml +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/.github/workflows/publish.yml +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/.sase_beads/beads.db +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/.sase_beads/config.json +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/.sase_beads/issues.jsonl +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/CLAUDE.md +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/Justfile +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/LICENSE +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/README.md +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/docs/architecture.md +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/docs/configuration.md +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/docs/xprompts.md +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/release-please-config.json +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/__init__.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/config.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/default_config.yml +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/errors.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/plugin.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/scripts/__init__.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/scripts/new_pr_desc_get_context.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/task_types.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/workspace_plugin.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/xprompts/new_pr_desc.yml +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/xprompts/pr_diff.yml +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/src/sase_github/xprompts/prdd.yml +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/tests/__init__.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_ci_install_contract.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_config.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_github_issues.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_github_plugin.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_github_pull_requests.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_submit_with_recorded_pr.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_task_types.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/tests/test_workspace_plugin.py +0 -0
- {sase_github-0.2.7 → sase_github-0.2.9}/uv.lock +0 -0
|
@@ -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
|
|
|
@@ -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
|
|
123
|
-
from sase.workspace_provider.occupant import clear_occupant_record
|
|
123
|
+
from sase_github.scripts.gh_release import main
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
{{ setup.project_file | tojson }},
|
|
127
|
-
{{ setup.workspace_num }},
|
|
128
|
-
{{ setup.
|
|
129
|
-
{{
|
|
130
|
-
|
|
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
|
|
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=
|
|
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
|
|
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
|
|
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
|
|
946
|
-
|
|
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 "
|
|
950
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|