sase-github 0.2.6__tar.gz → 0.2.8__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.8/.release-please-manifest.json +3 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/CHANGELOG.md +14 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/PKG-INFO +1 -1
- {sase_github-0.2.6 → sase_github-0.2.8}/pyproject.toml +1 -1
- {sase_github-0.2.6 → sase_github-0.2.8}/src/sase_github/scripts/gh_setup.py +35 -6
- {sase_github-0.2.6 → sase_github-0.2.8}/src/sase_github/xprompts/gh.yml +1 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/tests/test_gh_workspace_claims.py +183 -0
- sase_github-0.2.6/.release-please-manifest.json +0 -3
- {sase_github-0.2.6 → sase_github-0.2.8}/.github/workflows/ci.yml +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/.github/workflows/pr-title.yml +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/.github/workflows/publish.yml +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/.sase_beads/beads.db +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/.sase_beads/config.json +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/.sase_beads/issues.jsonl +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/CLAUDE.md +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/Justfile +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/LICENSE +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/README.md +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/docs/architecture.md +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/docs/configuration.md +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/docs/xprompts.md +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/release-please-config.json +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/src/sase_github/__init__.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/src/sase_github/config.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/src/sase_github/default_config.yml +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/src/sase_github/errors.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/src/sase_github/plugin.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/src/sase_github/scripts/__init__.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/src/sase_github/scripts/new_pr_desc_get_context.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/src/sase_github/task_types.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/src/sase_github/workspace_plugin.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/src/sase_github/xprompts/new_pr_desc.yml +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/src/sase_github/xprompts/pr_diff.yml +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/src/sase_github/xprompts/prdd.yml +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/tests/__init__.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/tests/test_ci_install_contract.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/tests/test_config.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/tests/test_github_issues.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/tests/test_github_plugin.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/tests/test_github_pull_requests.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/tests/test_submit_with_recorded_pr.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/tests/test_task_types.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/tests/test_workspace_plugin.py +0 -0
- {sase_github-0.2.6 → sase_github-0.2.8}/uv.lock +0 -0
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.8](https://github.com/sase-org/sase-github/compare/v0.2.7...v0.2.8) (2026-08-29)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **gh:** mark runner-bound workspace allocations ([f4663ae](https://github.com/sase-org/sase-github/commit/f4663ae526929b23378372005662c20b495bd0f7))
|
|
9
|
+
|
|
10
|
+
## [0.2.7](https://github.com/sase-org/sase-github/compare/v0.2.6...v0.2.7) (2026-08-28)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **gh-setup:** adopt the runner's numbered workspace claim ([5e8e9ea](https://github.com/sase-org/sase-github/commit/5e8e9ea6a48b5b285a65d9cb1fa087f74d52b6b0))
|
|
16
|
+
|
|
3
17
|
## [0.2.6](https://github.com/sase-org/sase-github/compare/v0.2.5...v0.2.6) (2026-08-18)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -8,8 +8,10 @@ from sase.running_field import (
|
|
|
8
8
|
WorkspaceClaim,
|
|
9
9
|
claim_next_axe_workspace,
|
|
10
10
|
claim_workspace,
|
|
11
|
+
find_runner_numbered_workspace,
|
|
11
12
|
get_claimed_workspaces,
|
|
12
13
|
release_workspace,
|
|
14
|
+
runner_has_placeholder_workspace,
|
|
13
15
|
)
|
|
14
16
|
from sase.sdd.store import materialize_sdd_store
|
|
15
17
|
from sase.workspace_provider import resolve_ref
|
|
@@ -43,6 +45,7 @@ def main(
|
|
|
43
45
|
# then incorrectly remove the RUNNING entry while the real workflow
|
|
44
46
|
# (the parent process) is still alive.
|
|
45
47
|
pid = os.getppid()
|
|
48
|
+
runner_bound_workspace = False
|
|
46
49
|
|
|
47
50
|
# Skip claiming when pre-allocated — the launcher already claimed the workspace
|
|
48
51
|
pre_allocated = os.environ.get("SASE_GH_PRE_ALLOCATED") == "1"
|
|
@@ -61,14 +64,25 @@ def main(
|
|
|
61
64
|
pinned=not release,
|
|
62
65
|
)
|
|
63
66
|
else:
|
|
64
|
-
|
|
67
|
+
adopted = _adopt_runner_workspace(
|
|
65
68
|
project_file=project_file,
|
|
66
69
|
primary_workspace_dir=resolved.primary_workspace_dir,
|
|
67
|
-
workflow_name=workflow_name,
|
|
68
|
-
pid=pid,
|
|
69
|
-
cl_name=cl_name,
|
|
70
|
-
pinned=not release,
|
|
71
70
|
)
|
|
71
|
+
if adopted is not None:
|
|
72
|
+
workspace_num, workspace_dir = adopted
|
|
73
|
+
pre_allocated = True
|
|
74
|
+
else:
|
|
75
|
+
runner_bound_workspace = runner_has_placeholder_workspace(
|
|
76
|
+
project_file, pid=pid
|
|
77
|
+
)
|
|
78
|
+
workspace_num, workspace_dir = _claim_next_workspace(
|
|
79
|
+
project_file=project_file,
|
|
80
|
+
primary_workspace_dir=resolved.primary_workspace_dir,
|
|
81
|
+
workflow_name=workflow_name,
|
|
82
|
+
pid=pid,
|
|
83
|
+
cl_name=cl_name,
|
|
84
|
+
pinned=not release,
|
|
85
|
+
)
|
|
72
86
|
|
|
73
87
|
# Refuse to hand this checkout to `prepare`/`checkout` if another live
|
|
74
88
|
# agent still occupies it. Runs on the pre_allocated branch too: the
|
|
@@ -122,13 +136,28 @@ def main(
|
|
|
122
136
|
print(f"checkout_target={resolved.checkout_target}")
|
|
123
137
|
print(f"primary_workspace_dir={resolved.primary_workspace_dir}")
|
|
124
138
|
# Don't release pre-allocated workspaces — the launcher handles that
|
|
125
|
-
should_release = release and not pre_allocated
|
|
139
|
+
should_release = release and not pre_allocated and not runner_bound_workspace
|
|
126
140
|
print(f"should_release={'true' if should_release else 'false'}")
|
|
141
|
+
print(f"runner_bound_workspace={'true' if runner_bound_workspace else 'false'}")
|
|
127
142
|
print(f"_chdir={workspace_dir}")
|
|
128
143
|
print(f"meta_workspace={workspace_num}")
|
|
129
144
|
print(f"workflow_name={workflow_name}")
|
|
130
145
|
|
|
131
146
|
|
|
147
|
+
def _adopt_runner_workspace(
|
|
148
|
+
*,
|
|
149
|
+
project_file: str,
|
|
150
|
+
primary_workspace_dir: str,
|
|
151
|
+
) -> tuple[int, str] | None:
|
|
152
|
+
"""Reuse the calling runner's numbered pool claim, if it holds one."""
|
|
153
|
+
workspace_num = find_runner_numbered_workspace(project_file)
|
|
154
|
+
if workspace_num is None:
|
|
155
|
+
return None
|
|
156
|
+
workspace_dir = ensure_workspace_checkout(primary_workspace_dir, workspace_num)
|
|
157
|
+
materialize_sdd_store(workspace_dir, workspace_num)
|
|
158
|
+
return workspace_num, workspace_dir
|
|
159
|
+
|
|
160
|
+
|
|
132
161
|
def _claim_next_workspace(
|
|
133
162
|
*,
|
|
134
163
|
project_file: str,
|
|
@@ -567,6 +567,189 @@ class TestGhSetupOccupancyGuard:
|
|
|
567
567
|
] == [10]
|
|
568
568
|
|
|
569
569
|
|
|
570
|
+
class TestGhSetupAdoptsRunnerWorkspace:
|
|
571
|
+
def test_adopts_parent_numbered_claim_without_second_claim(
|
|
572
|
+
self, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
573
|
+
) -> None:
|
|
574
|
+
parent_claim = WorkspaceClaim(
|
|
575
|
+
12, "ace(run)-launcher", "feature", pid=os.getppid()
|
|
576
|
+
)
|
|
577
|
+
project_file = _write_project_file(tmp_path, running_claims=[parent_claim])
|
|
578
|
+
workspace_dir = tmp_path / "widget_12"
|
|
579
|
+
workspace_dir.mkdir()
|
|
580
|
+
|
|
581
|
+
with (
|
|
582
|
+
_isolate_ledger(tmp_path),
|
|
583
|
+
patch(
|
|
584
|
+
"sase_github.scripts.gh_setup.resolve_ref",
|
|
585
|
+
return_value=_resolved(project_file),
|
|
586
|
+
),
|
|
587
|
+
patch.dict(os.environ, {"SASE_GH_PRE_ALLOCATED": "0"}),
|
|
588
|
+
patch(
|
|
589
|
+
"sase_github.scripts.gh_setup.ensure_workspace_checkout",
|
|
590
|
+
return_value=str(workspace_dir),
|
|
591
|
+
),
|
|
592
|
+
patch("sase_github.scripts.gh_setup.materialize_sdd_store") as materialize,
|
|
593
|
+
patch(
|
|
594
|
+
"sase_github.scripts.gh_setup.claim_next_axe_workspace"
|
|
595
|
+
) as claim_next,
|
|
596
|
+
patch("sase_github.scripts.gh_setup.claim_workspace") as claim,
|
|
597
|
+
):
|
|
598
|
+
gh_setup.main(gh_ref="acme/widget", n=None, release=True)
|
|
599
|
+
|
|
600
|
+
claim_next.assert_not_called()
|
|
601
|
+
claim.assert_not_called()
|
|
602
|
+
materialize.assert_called_once_with(str(workspace_dir), 12)
|
|
603
|
+
out = capsys.readouterr().out
|
|
604
|
+
assert "workspace_num=12" in out
|
|
605
|
+
assert "should_release=false" in out
|
|
606
|
+
claims = get_claimed_workspaces(project_file)
|
|
607
|
+
assert [claim.workspace_num for claim in claims] == [12]
|
|
608
|
+
assert claims[0].pid == os.getppid()
|
|
609
|
+
assert read_occupant_record(str(workspace_dir)) is None
|
|
610
|
+
|
|
611
|
+
def test_parent_placeholder_allocation_is_runner_bound(
|
|
612
|
+
self, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
613
|
+
) -> None:
|
|
614
|
+
placeholder = WorkspaceClaim(
|
|
615
|
+
0, "ace(run)-launcher", "feature", pid=os.getppid()
|
|
616
|
+
)
|
|
617
|
+
project_file = _write_project_file(tmp_path, running_claims=[placeholder])
|
|
618
|
+
|
|
619
|
+
with (
|
|
620
|
+
_isolate_ledger(tmp_path),
|
|
621
|
+
patch(
|
|
622
|
+
"sase_github.scripts.gh_setup.resolve_ref",
|
|
623
|
+
return_value=_resolved(project_file),
|
|
624
|
+
),
|
|
625
|
+
patch.dict(os.environ, {"SASE_GH_PRE_ALLOCATED": "0"}),
|
|
626
|
+
patch(
|
|
627
|
+
"sase_github.scripts.gh_setup.ensure_workspace_checkout",
|
|
628
|
+
return_value="/work/widget_10/",
|
|
629
|
+
),
|
|
630
|
+
patch("sase_github.scripts.gh_setup.materialize_sdd_store"),
|
|
631
|
+
):
|
|
632
|
+
gh_setup.main(gh_ref="acme/widget", n=None, release=True)
|
|
633
|
+
|
|
634
|
+
claimed_nums = {
|
|
635
|
+
claim.workspace_num for claim in get_claimed_workspaces(project_file)
|
|
636
|
+
}
|
|
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()
|
|
641
|
+
out = capsys.readouterr().out
|
|
642
|
+
assert "workspace_num=10" in out
|
|
643
|
+
assert "should_release=false" in out
|
|
644
|
+
assert "runner_bound_workspace=true" in out
|
|
645
|
+
|
|
646
|
+
def test_pinned_n_does_not_adopt_parent_claim(
|
|
647
|
+
self, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
648
|
+
) -> None:
|
|
649
|
+
parent_claim = WorkspaceClaim(
|
|
650
|
+
12, "ace(run)-launcher", "feature", pid=os.getppid()
|
|
651
|
+
)
|
|
652
|
+
project_file = _write_project_file(tmp_path, running_claims=[parent_claim])
|
|
653
|
+
|
|
654
|
+
with (
|
|
655
|
+
_isolate_ledger(tmp_path),
|
|
656
|
+
patch(
|
|
657
|
+
"sase_github.scripts.gh_setup.resolve_ref",
|
|
658
|
+
return_value=_resolved(project_file),
|
|
659
|
+
),
|
|
660
|
+
patch.dict(os.environ, {"SASE_GH_PRE_ALLOCATED": "0"}),
|
|
661
|
+
patch(
|
|
662
|
+
"sase_github.scripts.gh_setup.ensure_workspace_checkout",
|
|
663
|
+
return_value="/work/widget_17/",
|
|
664
|
+
),
|
|
665
|
+
patch("sase_github.scripts.gh_setup.materialize_sdd_store"),
|
|
666
|
+
):
|
|
667
|
+
gh_setup.main(gh_ref="acme/widget", n=17, release=True)
|
|
668
|
+
|
|
669
|
+
claimed_nums = {
|
|
670
|
+
claim.workspace_num for claim in get_claimed_workspaces(project_file)
|
|
671
|
+
}
|
|
672
|
+
assert claimed_nums == {12, 17}
|
|
673
|
+
out = capsys.readouterr().out
|
|
674
|
+
assert "workspace_num=17" in out
|
|
675
|
+
assert "should_release=true" in out
|
|
676
|
+
|
|
677
|
+
def test_adopted_run_does_not_rewrite_occupant_record(self, tmp_path: Path) -> None:
|
|
678
|
+
parent_claim = WorkspaceClaim(
|
|
679
|
+
12, "ace(run)-launcher", "feature", pid=os.getppid()
|
|
680
|
+
)
|
|
681
|
+
project_file = _write_project_file(tmp_path, running_claims=[parent_claim])
|
|
682
|
+
workspace_dir = tmp_path / "widget_12"
|
|
683
|
+
workspace_dir.mkdir()
|
|
684
|
+
original = new_occupant_record(
|
|
685
|
+
pid=os.getppid(),
|
|
686
|
+
workflow="ace(run)-launcher",
|
|
687
|
+
project="gh_acme__widget",
|
|
688
|
+
workspace_num=12,
|
|
689
|
+
agent_name="launcher-agent",
|
|
690
|
+
)
|
|
691
|
+
write_occupant_record(str(workspace_dir), original)
|
|
692
|
+
|
|
693
|
+
with (
|
|
694
|
+
_isolate_ledger(tmp_path),
|
|
695
|
+
patch(
|
|
696
|
+
"sase_github.scripts.gh_setup.resolve_ref",
|
|
697
|
+
return_value=_resolved(project_file),
|
|
698
|
+
),
|
|
699
|
+
patch.dict(os.environ, {"SASE_GH_PRE_ALLOCATED": "0"}),
|
|
700
|
+
patch(
|
|
701
|
+
"sase_github.scripts.gh_setup.ensure_workspace_checkout",
|
|
702
|
+
return_value=str(workspace_dir),
|
|
703
|
+
),
|
|
704
|
+
patch("sase_github.scripts.gh_setup.materialize_sdd_store"),
|
|
705
|
+
):
|
|
706
|
+
gh_setup.main(gh_ref="acme/widget", n=None, release=True)
|
|
707
|
+
|
|
708
|
+
record = read_occupant_record(str(workspace_dir))
|
|
709
|
+
assert record is not None
|
|
710
|
+
assert record.pid == original.pid
|
|
711
|
+
assert record.workflow == "ace(run)-launcher"
|
|
712
|
+
assert record.agent_name == "launcher-agent"
|
|
713
|
+
|
|
714
|
+
def test_adopted_guard_refusal_releases_nothing(self, tmp_path: Path) -> None:
|
|
715
|
+
parent_claim = WorkspaceClaim(
|
|
716
|
+
12, "ace(run)-launcher", "feature", pid=os.getppid()
|
|
717
|
+
)
|
|
718
|
+
project_file = _write_project_file(tmp_path, running_claims=[parent_claim])
|
|
719
|
+
workspace_dir = tmp_path / "widget_12"
|
|
720
|
+
workspace_dir.mkdir()
|
|
721
|
+
write_occupant_record(
|
|
722
|
+
str(workspace_dir),
|
|
723
|
+
new_occupant_record(
|
|
724
|
+
pid=os.getpid(),
|
|
725
|
+
workflow="ace(run)-rival",
|
|
726
|
+
project="gh_acme__widget",
|
|
727
|
+
workspace_num=12,
|
|
728
|
+
agent_name="rival-agent",
|
|
729
|
+
),
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
with (
|
|
733
|
+
_isolate_ledger(tmp_path),
|
|
734
|
+
patch(
|
|
735
|
+
"sase_github.scripts.gh_setup.resolve_ref",
|
|
736
|
+
return_value=_resolved(project_file),
|
|
737
|
+
),
|
|
738
|
+
patch.dict(os.environ, {"SASE_GH_PRE_ALLOCATED": "0"}),
|
|
739
|
+
patch(
|
|
740
|
+
"sase_github.scripts.gh_setup.ensure_workspace_checkout",
|
|
741
|
+
return_value=str(workspace_dir),
|
|
742
|
+
),
|
|
743
|
+
patch("sase_github.scripts.gh_setup.materialize_sdd_store"),
|
|
744
|
+
pytest.raises(WorkspaceOccupiedError, match="rival-agent"),
|
|
745
|
+
):
|
|
746
|
+
gh_setup.main(gh_ref="acme/widget", n=None, release=True)
|
|
747
|
+
|
|
748
|
+
assert [
|
|
749
|
+
claim.workspace_num for claim in get_claimed_workspaces(project_file)
|
|
750
|
+
] == [12]
|
|
751
|
+
|
|
752
|
+
|
|
570
753
|
class TestWsSubmitAtomicClaim:
|
|
571
754
|
def test_submit_acquires_through_claim_next_dir_and_releases(
|
|
572
755
|
self, tmp_path: Path
|
|
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
|