sase-github 0.2.4__tar.gz → 0.2.6__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.6/.release-please-manifest.json +3 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/CHANGELOG.md +20 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/CLAUDE.md +1 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/PKG-INFO +1 -1
- {sase_github-0.2.4 → sase_github-0.2.6}/README.md +4 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/docs/architecture.md +5 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/pyproject.toml +4 -1
- sase_github-0.2.6/src/sase_github/scripts/gh_setup.py +257 -0
- sase_github-0.2.6/src/sase_github/task_types.py +46 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/src/sase_github/workspace_plugin.py +32 -21
- {sase_github-0.2.4 → sase_github-0.2.6}/src/sase_github/xprompts/gh.yml +3 -0
- sase_github-0.2.6/tests/test_gh_workspace_claims.py +771 -0
- sase_github-0.2.6/tests/test_task_types.py +45 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/tests/test_workspace_plugin.py +29 -11
- sase_github-0.2.4/.release-please-manifest.json +0 -3
- sase_github-0.2.4/src/sase_github/scripts/gh_setup.py +0 -78
- {sase_github-0.2.4 → sase_github-0.2.6}/.github/workflows/ci.yml +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/.github/workflows/pr-title.yml +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/.github/workflows/publish.yml +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/.sase_beads/beads.db +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/.sase_beads/config.json +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/.sase_beads/issues.jsonl +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/Justfile +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/LICENSE +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/docs/configuration.md +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/docs/xprompts.md +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/release-please-config.json +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/src/sase_github/__init__.py +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/src/sase_github/config.py +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/src/sase_github/default_config.yml +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/src/sase_github/errors.py +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/src/sase_github/plugin.py +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/src/sase_github/scripts/__init__.py +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/src/sase_github/scripts/new_pr_desc_get_context.py +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/src/sase_github/xprompts/new_pr_desc.yml +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/src/sase_github/xprompts/pr_diff.yml +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/src/sase_github/xprompts/prdd.yml +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/tests/__init__.py +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/tests/test_ci_install_contract.py +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/tests/test_config.py +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/tests/test_github_issues.py +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/tests/test_github_plugin.py +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/tests/test_github_pull_requests.py +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/tests/test_submit_with_recorded_pr.py +0 -0
- {sase_github-0.2.4 → sase_github-0.2.6}/uv.lock +0 -0
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.6](https://github.com/sase-org/sase-github/compare/v0.2.5...v0.2.6) (2026-08-18)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* claim GitHub workspaces atomically ([61dd36f](https://github.com/sase-org/sase-github/commit/61dd36fa1aef7ec71475608e19de3ddf91b67b74))
|
|
9
|
+
* **workspace:** guard gh workflow steps against occupied checkouts ([51347d4](https://github.com/sase-org/sase-github/commit/51347d4f0a256c80785e7e2633fa589259073481))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **workspace:** release the gh setup claim when the occupancy guard refuses ([99a6277](https://github.com/sase-org/sase-github/commit/99a6277ecceb47d702c42351623ed27fbed11822))
|
|
15
|
+
|
|
16
|
+
## [0.2.5](https://github.com/sase-org/sase-github/compare/v0.2.4...v0.2.5) (2026-08-18)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* register agent-uncreatable github task type ([a68b879](https://github.com/sase-org/sase-github/commit/a68b879ba9a8a0ca5dc6f253a0c66563f9a90d4b))
|
|
22
|
+
|
|
3
23
|
## [0.2.4](https://github.com/sase-org/sase-github/compare/v0.2.3...v0.2.4) (2026-08-11)
|
|
4
24
|
|
|
5
25
|
|
|
@@ -15,6 +15,7 @@ just check # lint + test
|
|
|
15
15
|
|
|
16
16
|
## Architecture
|
|
17
17
|
- `src/sase_github/plugin.py` — GitHubPlugin class (extends `sase.vcs_provider.plugins._git_common.GitCommon`)
|
|
18
|
+
- `src/sase_github/task_types.py` — agent-uncreatable `github` task type via `sase_task_types`
|
|
18
19
|
- `src/sase_github/xprompts/` — GitHub workflow YAML files discovered via `sase_xprompts` entry point
|
|
19
20
|
- Depends on `sase>=0.1.3` for base classes, hookspec, and script modules
|
|
20
21
|
|
|
@@ -76,6 +76,10 @@ Requires `sase>=0.11.0` as a dependency. For GitHub Enterprise Server or self-ho
|
|
|
76
76
|
- **`get_github_orgs()`** — Reads `github_orgs` from sase config to determine SSH vs HTTPS clone URLs for
|
|
77
77
|
organizations/users with push access, and to seed local `#gh:` owner completion
|
|
78
78
|
|
|
79
|
+
### Task type
|
|
80
|
+
|
|
81
|
+
- **`github`** — Agent-uncreatable task type registered through the `sase_task_types` entry point. Beads of this type are created by SASE's external issue mirror, not by agents.
|
|
82
|
+
|
|
79
83
|
### XPrompts
|
|
80
84
|
|
|
81
85
|
| XPrompt | Description |
|
|
@@ -14,6 +14,11 @@ Registered in `pyproject.toml`:
|
|
|
14
14
|
| `sase_workspace:github` | `GitHubWorkspacePlugin` | Workspace orchestration (ref resolution, submission, mail prep) |
|
|
15
15
|
| `sase_xprompts:sase_github` | — | Makes `#gh`, `#new_pr_desc`, `#prdd` xprompts discoverable |
|
|
16
16
|
| `sase_config:sase_github` | — | Contributes `default_config.yml` to the sase config chain |
|
|
17
|
+
| `sase_task_types:github` | `GITHUB_TASK_TYPES` | Agent-uncreatable `github` task type for mirrored issues |
|
|
18
|
+
|
|
19
|
+
### GitHub task type (`task_types.py`)
|
|
20
|
+
|
|
21
|
+
Registers one `sase_task_types` spec: `task_type: github`, `agent_creatable: false`, no extra fields. Agents never create this type. SASE's external issue mirror stamps it on new mirrored task beads. This is not an `IssueType`; mirrored issues stay `task` beads with an `external_ref`.
|
|
17
22
|
|
|
18
23
|
### GitHubPlugin (`plugin.py`)
|
|
19
24
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sase-github"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.6"
|
|
8
8
|
description = "GitHub VCS plugin for sase"
|
|
9
9
|
requires-python = ">=3.12"
|
|
10
10
|
license = "MIT"
|
|
@@ -32,6 +32,9 @@ sase_github = "sase_github"
|
|
|
32
32
|
[project.entry-points."sase_xprompts"]
|
|
33
33
|
sase_github = "sase_github"
|
|
34
34
|
|
|
35
|
+
[project.entry-points."sase_task_types"]
|
|
36
|
+
github = "sase_github.task_types:GITHUB_TASK_TYPES"
|
|
37
|
+
|
|
35
38
|
[tool.hatch.build.targets.wheel]
|
|
36
39
|
packages = ["src/sase_github"]
|
|
37
40
|
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Setup step for the #gh xprompt workflow."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from sase.core.occupancy_guard import OccupancyCaller, ensure_workspace_not_occupied
|
|
7
|
+
from sase.running_field import (
|
|
8
|
+
WorkspaceClaim,
|
|
9
|
+
claim_next_axe_workspace,
|
|
10
|
+
claim_workspace,
|
|
11
|
+
get_claimed_workspaces,
|
|
12
|
+
release_workspace,
|
|
13
|
+
)
|
|
14
|
+
from sase.sdd.store import materialize_sdd_store
|
|
15
|
+
from sase.workspace_provider import resolve_ref
|
|
16
|
+
from sase.workspace_provider.occupant import new_occupant_record, write_occupant_record
|
|
17
|
+
from sase.workspace_provider.utils import ensure_workspace_checkout
|
|
18
|
+
|
|
19
|
+
_CALLER_TAG = "gh-setup"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main(
|
|
23
|
+
*,
|
|
24
|
+
gh_ref: str,
|
|
25
|
+
n: int | None,
|
|
26
|
+
release: bool,
|
|
27
|
+
workflow_label: str | None = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Resolve GitHub ref, claim a workspace, and print config.
|
|
30
|
+
|
|
31
|
+
Prints key=value output for the workflow executor.
|
|
32
|
+
"""
|
|
33
|
+
resolved = resolve_ref(gh_ref, "gh")
|
|
34
|
+
|
|
35
|
+
project_name = resolved.project_name
|
|
36
|
+
project_file = resolved.project_file
|
|
37
|
+
workflow_name = workflow_label or f"gh-{gh_ref}"
|
|
38
|
+
cl_name = None if "/" in gh_ref else gh_ref
|
|
39
|
+
|
|
40
|
+
# Use the parent process PID, not our own. This setup step runs as a
|
|
41
|
+
# short-lived subprocess (via ``subprocess.run``), so ``os.getpid()``
|
|
42
|
+
# would die immediately after setup. ``stale_running_cleanup`` would
|
|
43
|
+
# then incorrectly remove the RUNNING entry while the real workflow
|
|
44
|
+
# (the parent process) is still alive.
|
|
45
|
+
pid = os.getppid()
|
|
46
|
+
|
|
47
|
+
# Skip claiming when pre-allocated — the launcher already claimed the workspace
|
|
48
|
+
pre_allocated = os.environ.get("SASE_GH_PRE_ALLOCATED") == "1"
|
|
49
|
+
if pre_allocated:
|
|
50
|
+
workspace_num = int(os.environ["SASE_GH_WORKSPACE_NUM"])
|
|
51
|
+
workspace_dir = os.environ["SASE_GH_WORKSPACE_DIR"]
|
|
52
|
+
materialize_sdd_store(workspace_dir, workspace_num)
|
|
53
|
+
elif n is not None:
|
|
54
|
+
workspace_num, workspace_dir = _claim_pinned_workspace(
|
|
55
|
+
project_file=project_file,
|
|
56
|
+
primary_workspace_dir=resolved.primary_workspace_dir,
|
|
57
|
+
workspace_num=n,
|
|
58
|
+
workflow_name=workflow_name,
|
|
59
|
+
pid=pid,
|
|
60
|
+
cl_name=cl_name,
|
|
61
|
+
pinned=not release,
|
|
62
|
+
)
|
|
63
|
+
else:
|
|
64
|
+
workspace_num, workspace_dir = _claim_next_workspace(
|
|
65
|
+
project_file=project_file,
|
|
66
|
+
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
|
+
)
|
|
72
|
+
|
|
73
|
+
# Refuse to hand this checkout to `prepare`/`checkout` if another live
|
|
74
|
+
# agent still occupies it. Runs on the pre_allocated branch too: the
|
|
75
|
+
# launcher already wrote its own occupant record, and the conflict
|
|
76
|
+
# decision recognises the caller's own lineage via the shared pid.
|
|
77
|
+
# A refusal here aborts the step, so the slot this run just claimed must
|
|
78
|
+
# go back — the workflow's `release` step never runs once `setup` fails.
|
|
79
|
+
# The pre_allocated branch claimed nothing, so it releases nothing.
|
|
80
|
+
try:
|
|
81
|
+
ensure_workspace_not_occupied(
|
|
82
|
+
workspace_dir,
|
|
83
|
+
project_file=project_file,
|
|
84
|
+
caller=OccupancyCaller(
|
|
85
|
+
pid=pid,
|
|
86
|
+
workspace_num=workspace_num,
|
|
87
|
+
project=project_name,
|
|
88
|
+
workflow=workflow_name,
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
except Exception:
|
|
92
|
+
if not pre_allocated:
|
|
93
|
+
release_workspace(
|
|
94
|
+
project_file,
|
|
95
|
+
workspace_num,
|
|
96
|
+
workflow_name,
|
|
97
|
+
cl_name,
|
|
98
|
+
caller_tag=_CALLER_TAG,
|
|
99
|
+
)
|
|
100
|
+
raise
|
|
101
|
+
|
|
102
|
+
# Claim the checkout for this run. Workspace numbers 0/1 both mean
|
|
103
|
+
# "primary", never a real numbered workspace (see
|
|
104
|
+
# ``ensure_workspace_checkout``), and the pre_allocated branch's
|
|
105
|
+
# occupant record already belongs to the launcher.
|
|
106
|
+
if not pre_allocated and workspace_num > 1:
|
|
107
|
+
write_occupant_record(
|
|
108
|
+
workspace_dir,
|
|
109
|
+
new_occupant_record(
|
|
110
|
+
pid=pid,
|
|
111
|
+
workflow=workflow_name,
|
|
112
|
+
project=project_name,
|
|
113
|
+
workspace_num=workspace_num,
|
|
114
|
+
cl_name=cl_name,
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
print(f"project_name={project_name}")
|
|
119
|
+
print(f"project_file={project_file}")
|
|
120
|
+
print(f"workspace_dir={workspace_dir}")
|
|
121
|
+
print(f"workspace_num={workspace_num}")
|
|
122
|
+
print(f"checkout_target={resolved.checkout_target}")
|
|
123
|
+
print(f"primary_workspace_dir={resolved.primary_workspace_dir}")
|
|
124
|
+
# Don't release pre-allocated workspaces — the launcher handles that
|
|
125
|
+
should_release = release and not pre_allocated
|
|
126
|
+
print(f"should_release={'true' if should_release else 'false'}")
|
|
127
|
+
print(f"_chdir={workspace_dir}")
|
|
128
|
+
print(f"meta_workspace={workspace_num}")
|
|
129
|
+
print(f"workflow_name={workflow_name}")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _claim_next_workspace(
|
|
133
|
+
*,
|
|
134
|
+
project_file: str,
|
|
135
|
+
primary_workspace_dir: str,
|
|
136
|
+
workflow_name: str,
|
|
137
|
+
pid: int,
|
|
138
|
+
cl_name: str | None,
|
|
139
|
+
pinned: bool,
|
|
140
|
+
) -> tuple[int, str]:
|
|
141
|
+
"""Atomically allocate a slot, then materialize the checkout."""
|
|
142
|
+
workspace_num = claim_next_axe_workspace(
|
|
143
|
+
project_file,
|
|
144
|
+
workflow_name,
|
|
145
|
+
pid,
|
|
146
|
+
cl_name=cl_name,
|
|
147
|
+
pinned=pinned,
|
|
148
|
+
caller_tag=_CALLER_TAG,
|
|
149
|
+
)
|
|
150
|
+
workspace_dir = _materialize_or_release(
|
|
151
|
+
project_file=project_file,
|
|
152
|
+
primary_workspace_dir=primary_workspace_dir,
|
|
153
|
+
workspace_num=workspace_num,
|
|
154
|
+
workflow_name=workflow_name,
|
|
155
|
+
cl_name=cl_name,
|
|
156
|
+
)
|
|
157
|
+
return workspace_num, workspace_dir
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _claim_pinned_workspace(
|
|
161
|
+
*,
|
|
162
|
+
project_file: str,
|
|
163
|
+
primary_workspace_dir: str,
|
|
164
|
+
workspace_num: int,
|
|
165
|
+
workflow_name: str,
|
|
166
|
+
pid: int,
|
|
167
|
+
cl_name: str | None,
|
|
168
|
+
pinned: bool,
|
|
169
|
+
) -> tuple[int, str]:
|
|
170
|
+
"""Claim a pinned ``n=<num>`` target once. Occupied slots fail with the occupant named."""
|
|
171
|
+
claim_result = claim_workspace(
|
|
172
|
+
project_file,
|
|
173
|
+
workspace_num,
|
|
174
|
+
workflow_name,
|
|
175
|
+
pid,
|
|
176
|
+
cl_name,
|
|
177
|
+
pinned=pinned,
|
|
178
|
+
caller_tag=_CALLER_TAG,
|
|
179
|
+
)
|
|
180
|
+
if not claim_result.success:
|
|
181
|
+
occupant = _describe_workspace_occupant(project_file, workspace_num)
|
|
182
|
+
if occupant is not None:
|
|
183
|
+
print(
|
|
184
|
+
f"Pinned workspace #{workspace_num} is already claimed by {occupant}",
|
|
185
|
+
file=sys.stderr,
|
|
186
|
+
)
|
|
187
|
+
else:
|
|
188
|
+
print(
|
|
189
|
+
f"Pinned workspace #{workspace_num} could not be claimed: "
|
|
190
|
+
f"{claim_result.error or 'unknown reason'}",
|
|
191
|
+
file=sys.stderr,
|
|
192
|
+
)
|
|
193
|
+
sys.exit(1)
|
|
194
|
+
workspace_dir = _materialize_or_release(
|
|
195
|
+
project_file=project_file,
|
|
196
|
+
primary_workspace_dir=primary_workspace_dir,
|
|
197
|
+
workspace_num=workspace_num,
|
|
198
|
+
workflow_name=workflow_name,
|
|
199
|
+
cl_name=cl_name,
|
|
200
|
+
)
|
|
201
|
+
return workspace_num, workspace_dir
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _materialize_or_release(
|
|
205
|
+
*,
|
|
206
|
+
project_file: str,
|
|
207
|
+
primary_workspace_dir: str,
|
|
208
|
+
workspace_num: int,
|
|
209
|
+
workflow_name: str,
|
|
210
|
+
cl_name: str | None,
|
|
211
|
+
) -> str:
|
|
212
|
+
"""Materialize after a successful claim; release the slot if setup fails."""
|
|
213
|
+
try:
|
|
214
|
+
workspace_dir = ensure_workspace_checkout(primary_workspace_dir, workspace_num)
|
|
215
|
+
materialize_sdd_store(workspace_dir, workspace_num)
|
|
216
|
+
except Exception:
|
|
217
|
+
release_workspace(
|
|
218
|
+
project_file,
|
|
219
|
+
workspace_num,
|
|
220
|
+
workflow_name,
|
|
221
|
+
cl_name,
|
|
222
|
+
caller_tag=_CALLER_TAG,
|
|
223
|
+
)
|
|
224
|
+
raise
|
|
225
|
+
return workspace_dir
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _describe_workspace_occupant(project_file: str, workspace_num: int) -> str | None:
|
|
229
|
+
occupants = [
|
|
230
|
+
claim
|
|
231
|
+
for claim in get_claimed_workspaces(project_file)
|
|
232
|
+
if claim.workspace_num == workspace_num
|
|
233
|
+
]
|
|
234
|
+
if not occupants:
|
|
235
|
+
return None
|
|
236
|
+
return "; ".join(_format_workspace_occupant(claim) for claim in occupants)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _format_workspace_occupant(claim: WorkspaceClaim) -> str:
|
|
240
|
+
name = claim.cl_name or claim.workflow
|
|
241
|
+
liveness = "live" if _pid_is_alive(claim.pid) else "dead"
|
|
242
|
+
detail = f"{name} (pid {claim.pid}, {liveness}, workflow {claim.workflow}"
|
|
243
|
+
if claim.artifacts_timestamp:
|
|
244
|
+
detail += f", artifacts {claim.artifacts_timestamp}"
|
|
245
|
+
return detail + ")"
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _pid_is_alive(pid: int) -> bool:
|
|
249
|
+
if pid <= 0:
|
|
250
|
+
return False
|
|
251
|
+
try:
|
|
252
|
+
os.kill(pid, 0)
|
|
253
|
+
except ProcessLookupError:
|
|
254
|
+
return False
|
|
255
|
+
except PermissionError:
|
|
256
|
+
return True
|
|
257
|
+
return True
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Declarative ``github`` task-type spec for the sase-github plugin.
|
|
2
|
+
|
|
3
|
+
Mirrored GitHub issues are already ``task`` beads created by sase's external
|
|
4
|
+
issue mirror. This spec stamps that origin as an agent-uncreatable catalog
|
|
5
|
+
member; it does not add an ``IssueType.GITHUB``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Mapping
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from sase.task_types import hookimpl
|
|
14
|
+
|
|
15
|
+
_TASK_TYPE_SPEC_SCHEMA_VERSION = 1
|
|
16
|
+
|
|
17
|
+
GITHUB_TASK_TYPE = "github"
|
|
18
|
+
|
|
19
|
+
GITHUB_TASK_TYPE_SPEC: Mapping[str, Any] = {
|
|
20
|
+
"schema_version": _TASK_TYPE_SPEC_SCHEMA_VERSION,
|
|
21
|
+
"task_type": GITHUB_TASK_TYPE,
|
|
22
|
+
"label": "GitHub",
|
|
23
|
+
"summary": (
|
|
24
|
+
"A GitHub issue mirrored into a task bead; agents never create this type."
|
|
25
|
+
),
|
|
26
|
+
"when_to_use": (
|
|
27
|
+
"Agents never create this type. Beads of it are created by the "
|
|
28
|
+
"external issue mirror when it covers a GitHub issue. Do not file "
|
|
29
|
+
"one from `sase bead create` or `/sase_new_task`."
|
|
30
|
+
),
|
|
31
|
+
"glyph": "⑂",
|
|
32
|
+
"accent_color": "#B2B2B2",
|
|
33
|
+
"agent_creatable": False,
|
|
34
|
+
"fields": [],
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class _GitHubTaskTypes:
|
|
39
|
+
"""Pluggy hookimpl exposing the ``github`` task-type spec."""
|
|
40
|
+
|
|
41
|
+
@hookimpl
|
|
42
|
+
def task_type_specs(self) -> tuple[Mapping[str, Any], ...]:
|
|
43
|
+
return (GITHUB_TASK_TYPE_SPEC,)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
GITHUB_TASK_TYPES = _GitHubTaskTypes()
|
|
@@ -487,10 +487,14 @@ class GitHubWorkspacePlugin:
|
|
|
487
487
|
kill_and_persist_all_running_processes,
|
|
488
488
|
)
|
|
489
489
|
from sase.ace.operations import has_active_children
|
|
490
|
+
from sase.core.occupancy_guard import (
|
|
491
|
+
OccupancyCaller,
|
|
492
|
+
WorkspaceOccupiedError,
|
|
493
|
+
ensure_workspace_not_occupied,
|
|
494
|
+
)
|
|
490
495
|
from sase.running_field import (
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
get_workspace_directory_for_num,
|
|
496
|
+
WorkspaceClaimError,
|
|
497
|
+
claim_next_axe_workspace_dir,
|
|
494
498
|
release_workspace,
|
|
495
499
|
)
|
|
496
500
|
from sase.vcs_provider import get_vcs_provider
|
|
@@ -536,36 +540,42 @@ class GitHubWorkspacePlugin:
|
|
|
536
540
|
if not workspace_dir:
|
|
537
541
|
return (False, "WORKSPACE_DIR is not set for this project")
|
|
538
542
|
|
|
539
|
-
workspace_num = get_first_available_axe_workspace(patch_file)
|
|
540
543
|
workflow_name = f"submit-{patch_name}"
|
|
541
|
-
pid = os.getpid()
|
|
542
|
-
|
|
543
544
|
try:
|
|
544
|
-
ws_dir, _ =
|
|
545
|
-
|
|
546
|
-
|
|
545
|
+
workspace_num, ws_dir, _ = claim_next_axe_workspace_dir(
|
|
546
|
+
patch_file,
|
|
547
|
+
workflow_name,
|
|
548
|
+
os.getpid(),
|
|
549
|
+
project_basename,
|
|
550
|
+
cl_name=patch_name,
|
|
551
|
+
caller_tag="gh-submit",
|
|
552
|
+
)
|
|
553
|
+
except WorkspaceClaimError as exc:
|
|
554
|
+
return (False, f"Failed to claim workspace: {exc}")
|
|
547
555
|
|
|
548
556
|
if rich_console:
|
|
549
557
|
rich_console.print(f"[cyan]Claiming workspace #{workspace_num}[/cyan]")
|
|
550
558
|
|
|
551
|
-
if not claim_workspace(
|
|
552
|
-
patch_file,
|
|
553
|
-
workspace_num,
|
|
554
|
-
workflow_name,
|
|
555
|
-
pid,
|
|
556
|
-
patch_name,
|
|
557
|
-
):
|
|
558
|
-
return (
|
|
559
|
-
False,
|
|
560
|
-
f"Failed to claim workspace #{workspace_num}",
|
|
561
|
-
)
|
|
562
|
-
|
|
563
559
|
try:
|
|
564
560
|
if rich_console:
|
|
565
561
|
rich_console.print(
|
|
566
562
|
f"[cyan]Checking out {escape_markup(patch_name)}...[/cyan]"
|
|
567
563
|
)
|
|
568
564
|
|
|
565
|
+
try:
|
|
566
|
+
ensure_workspace_not_occupied(
|
|
567
|
+
ws_dir,
|
|
568
|
+
project_file=patch_file,
|
|
569
|
+
caller=OccupancyCaller(
|
|
570
|
+
pid=os.getpid(),
|
|
571
|
+
workspace_num=workspace_num,
|
|
572
|
+
project=project_basename,
|
|
573
|
+
workflow=workflow_name,
|
|
574
|
+
),
|
|
575
|
+
)
|
|
576
|
+
except WorkspaceOccupiedError as exc:
|
|
577
|
+
return (False, f"Refusing checkout: {exc}")
|
|
578
|
+
|
|
569
579
|
provider = get_vcs_provider(ws_dir)
|
|
570
580
|
branch_name = provider.resolve_revision(
|
|
571
581
|
patch_name, project_basename, ws_dir
|
|
@@ -619,6 +629,7 @@ class GitHubWorkspacePlugin:
|
|
|
619
629
|
workspace_num,
|
|
620
630
|
workflow_name,
|
|
621
631
|
patch_name,
|
|
632
|
+
caller_tag="gh-submit",
|
|
622
633
|
)
|
|
623
634
|
if rich_console:
|
|
624
635
|
rich_console.print(f"[cyan]Released workspace #{workspace_num}[/cyan]")
|
|
@@ -120,13 +120,16 @@ steps:
|
|
|
120
120
|
if: "{{ setup.should_release }}"
|
|
121
121
|
python: |
|
|
122
122
|
from sase.running_field import release_workspace
|
|
123
|
+
from sase.workspace_provider.occupant import clear_occupant_record
|
|
123
124
|
|
|
124
125
|
release_workspace(
|
|
125
126
|
{{ setup.project_file | tojson }},
|
|
126
127
|
{{ setup.workspace_num }},
|
|
127
128
|
{{ setup.workflow_name | tojson }},
|
|
128
129
|
{{ gh_ref | tojson }} if "/" not in {{ gh_ref | tojson }} else None,
|
|
130
|
+
caller_tag="gh-release",
|
|
129
131
|
)
|
|
132
|
+
clear_occupant_record({{ setup.workspace_dir | tojson }})
|
|
130
133
|
print("released=true")
|
|
131
134
|
output: { released: bool }
|
|
132
135
|
|