diff --git a/docs/llm-workflow-runbooks.md b/docs/llm-workflow-runbooks.md index 3bcc708..4ea9c13 100644 --- a/docs/llm-workflow-runbooks.md +++ b/docs/llm-workflow-runbooks.md @@ -229,6 +229,34 @@ Legacy environment-only setups keep working unchanged until migrated. Each runbook names the **profile role** it runs under, the steps, and a safe prompt. Confirm the active profile first (`gitea_get_profile` / `gitea_whoami`). +## Work Selection Rule for LLMs + +Before starting any issue or PR work, acquire or verify a work lease. Do not +begin coding, reviewing, fixing, branching, committing, pushing, commenting, +or creating a PR until you prove the target is not already being worked. + +Required checks: + +1. List open PRs. +2. Search for PRs linked to the target issue. +3. Search local and remote branches for the issue number. +4. Search registered worktrees for the issue branch. +5. Check dirty worktrees. +6. Check active leases or recent handoffs. +7. Check whether the issue was already completed by a merged PR. + +If another active LLM/session owns the lease, stop. Allowed responses: +continue as the lease owner; review the existing PR if reviewer capability +allows; produce a handoff; request takeover after lease expiry; stop with +"work already claimed." + +Never create a parallel branch or PR for the same issue unless the old branch +is proven abandoned and the takeover is recorded. + +Gitea-Tools lease gates: `gitea_lock_issue` (fail-closed before author +mutations), `status:in-progress`, and claim comments. Full portable wording: +[`skills/llm-project-workflow/SKILL.md`](../skills/llm-project-workflow/SKILL.md). + ## Branch worktree isolation All LLM implementation and review work happens in an isolated branch worktree diff --git a/docs/wiki/Workflow.md b/docs/wiki/Workflow.md index 7722426..708e0a6 100644 --- a/docs/wiki/Workflow.md +++ b/docs/wiki/Workflow.md @@ -14,6 +14,9 @@ ## Step 2: Implement issues (author profile) +0. Work Selection Rule — verify a work lease before any mutations (open PRs, + issue-linked PRs, branches, worktrees, dirty worktrees, active leases/ + handoffs, merged-PR completion). Stop if another session owns the lease. 1. `gitea_resolve_task_capability` for the author task. 2. `gitea_lock_issue` before implementation mutations. 3. Claim with `gitea_mark_issue` / `status:in-progress` label. diff --git a/gitea_mcp_server.py b/gitea_mcp_server.py index 1c5c186..ebee897 100644 --- a/gitea_mcp_server.py +++ b/gitea_mcp_server.py @@ -3460,11 +3460,27 @@ _GUIDE_RULES = { "Conflicting or stale PR state (e.g. prior said merged but live open, or head/updated mismatch) is a hard blocker: report it explicitly and stop until live state is unambiguous.", "After merge, re-list PRs and re-view the PR (plus verify master contains the merge) before claiming completion.", ], + "work_selection": ( + "Before any issue or PR work, acquire or verify a work lease. Do not " + "code, review, branch, commit, push, comment, or open a PR until you " + "prove the target is not already being worked. Required checks: list " + "open PRs; search PRs linked to the issue; search local/remote " + "branches for the issue number; search worktrees for the issue " + "branch; check dirty worktrees; check active leases or recent " + "handoffs; check whether a merged PR already completed the issue. If " + "another session owns the lease, stop (continue as owner, review the " + "existing PR, hand off, request takeover after expiry, or report " + "'work already claimed'). Never create a parallel branch/PR unless " + "the old branch is proven abandoned and takeover is recorded. " + "gitea_lock_issue is the fail-closed author lease gate."), } _COMMON_WORKFLOWS = [ "task routing: resolve task capability via gitea_resolve_task_capability " "to check required permission/role kind and identify the safe next action.", + "work selection: acquire or verify a work lease before any issue/PR work " + "(open PRs, linked PRs, branches, worktrees, dirty worktrees, leases, " + "merged completion); stop if another session owns the lease.", "issue authoring: verify identity, create/claim the issue, keep scope " "explicit (remote/org/repo).", "implementation: claim issue, branch from fresh master, implement only " diff --git a/skills/llm-project-workflow/SKILL.md b/skills/llm-project-workflow/SKILL.md index d8ad9f6..d92aa0b 100644 --- a/skills/llm-project-workflow/SKILL.md +++ b/skills/llm-project-workflow/SKILL.md @@ -53,6 +53,42 @@ Additional issue-first rules: owner decision.** Do not create a new repository or a new tracker unless explicitly approved by the owner. +## Work Selection Rule for LLMs + +Before starting any issue or PR work, acquire or verify a work lease. + +Do not begin coding, reviewing, fixing, branching, committing, pushing, +commenting, or creating a PR until you prove the target is not already being +worked. + +Required checks: + +1. List open PRs. +2. Search for PRs linked to the target issue. +3. Search local and remote branches for the issue number. +4. Search registered worktrees for the issue branch. +5. Check dirty worktrees. +6. Check active leases or recent handoffs. +7. Check whether the issue was already completed by a merged PR. + +If another active LLM/session owns the lease, stop. + +Allowed responses: + +- continue as the lease owner, +- review the existing PR if reviewer capability allows, +- produce a handoff, +- request takeover after lease expiry, +- stop with "work already claimed." + +Never create a parallel branch or PR for the same issue unless the old branch +is proven abandoned and the takeover is recorded. + +For Gitea-Tools: `gitea_lock_issue` is the fail-closed lease gate before author +mutations; `status:in-progress` and claim comments are supporting lease signals. +Use `review_proofs.classify_issue_for_selection` when reporting fresh issue +selection (#188). + ## B. Isolated worktree rule **Never implement or review in the main checkout.** The main checkout is for @@ -142,6 +178,8 @@ Worktree folder = branch with `/` replaced by `-` ## E. Start-work workflow +0. Acquire or verify a work lease (Work Selection Rule) — complete all seven + checks before any claim, branch, or PR work. 1. Verify the orchestration checkout (right repo, clean tree). 2. Fetch/prune: `git fetch --prune`. 3. Confirm local `master` equals remote `master` (`git rev-list --left-right --count /master...master` → `0 0`). diff --git a/skills/llm-project-workflow/templates/start-issue.md b/skills/llm-project-workflow/templates/start-issue.md index 54933fc..bde01e7 100644 --- a/skills/llm-project-workflow/templates/start-issue.md +++ b/skills/llm-project-workflow/templates/start-issue.md @@ -13,6 +13,15 @@ Rules (llm-project-workflow): - Do not self-review or self-merge. Steps: +0. Work Selection Rule — before any claim, branch, or file edits, acquire or + verify a work lease. Required checks: list open PRs; search PRs linked to + the target issue; search local/remote branches for the issue number; search + registered worktrees for the issue branch; check dirty worktrees; check + active leases or recent handoffs; check whether a merged PR already + completed the issue. If another session owns the lease, stop (continue only + as lease owner, review the existing PR, hand off, request takeover after + expiry, or report "work already claimed"). Never open a parallel branch/PR + unless the old branch is proven abandoned and takeover is recorded. 1. Identity Checklist: Before claiming work, verify and state: - Required identity/profile for this task: author (allowed to push branches / create PRs) - Current authenticated identity (from whoami): diff --git a/tests/test_operator_guide.py b/tests/test_operator_guide.py index 80c07f4..01dd46e 100644 --- a/tests/test_operator_guide.py +++ b/tests/test_operator_guide.py @@ -132,7 +132,8 @@ class TestControlPlaneGuide(GuideTestBase): rules = g["rules"] for key in ("hard_stops", "fail_closed", "head_sha_pinning", "merge_confirmation", "redaction", "separation", - "profile_switching", "identity_verification"): + "profile_switching", "identity_verification", + "work_selection"): self.assertIn(key, rules) self.assertIn("MERGE PR", json.dumps(rules["merge_confirmation"])) self.assertTrue(rules["hard_stops"]) diff --git a/tests/test_review_proofs.py b/tests/test_review_proofs.py index 3965880..e61e98b 100644 --- a/tests/test_review_proofs.py +++ b/tests/test_review_proofs.py @@ -1051,6 +1051,8 @@ class TestControllerHandoff(unittest.TestCase): self.assertIn("## Controller Handoff", skill) self.assertIn("assess_controller_handoff", skill) self.assertIn("issue #182", skill) + self.assertIn("## Work Selection Rule for LLMs", skill) + self.assertIn("work already claimed", skill) def test_handoff_rejects_none_workspace_mutations_when_local_edits_exist(self): # 1. Workspace mutations: none is rejected when local_edits is True