feat(author-workflow): enforce exact issue lock before branch/commit/push/PR (Issue #204)
Recreation of the #204 work from closed PR #205 (invalid provenance), rebuilt cleanly on master under the prgs author identity with no PR #203 content: - Add gitea_lock_issue MCP tool: locks exactly one issue to its branch name, fails closed on branch/issue-number mismatch and on issues already tied to an open PR (by head branch or Closes/Fixes reference). - gitea_create_pr now requires the issue lock: head must match the locked branch, title/body must contain Closes/Fixes #<locked issue> exactly, and ambiguous references (equivalent / related / same as) are rejected. - scripts/worktree-start refuses to create an issue-linked worktree unless the lock file exists and matches the requested branch. - assess_controller_handoff rejects handoffs whose selected issue / opened PR fields carry multiple numbers or fuzzy equivalence wording. - Tests: TestIssueLocking (lock + create_pr gates), handoff exact-reference tests, worktree-start lock coverage. Closes #204 Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
@@ -846,6 +846,35 @@ def assess_controller_handoff(report_text, role=None):
|
||||
"reasons": [f"handoff missing required field: {m}"
|
||||
for m in missing],
|
||||
}
|
||||
|
||||
# Validate issue/PR references for exact number and no forbidden terms (Issue #194 / #196)
|
||||
fields_dict = {}
|
||||
for line in section:
|
||||
stripped = line.strip().lstrip("-*").strip()
|
||||
if ":" in stripped:
|
||||
k, v = stripped.split(":", 1)
|
||||
fields_dict[k.strip().lower()] = v.strip()
|
||||
|
||||
for alias in ("selected issue", "pr number opened", "pr opened", "pr number", "selected pr"):
|
||||
val = fields_dict.get(alias)
|
||||
if val:
|
||||
numbers = re.findall(r"\d+", val)
|
||||
has_forbidden = any(term in val.lower() for term in ("equivalent", "related", "same", "/"))
|
||||
if len(numbers) != 1 or has_forbidden:
|
||||
field_name = "Selected issue/PR"
|
||||
for name, aliases in list(HANDOFF_BASE_FIELDS) + list(HANDOFF_ROLE_FIELDS.get(role or "", ())):
|
||||
if alias in aliases:
|
||||
field_name = name
|
||||
break
|
||||
return {
|
||||
"verdict": "incomplete",
|
||||
"downgraded": True,
|
||||
"missing_fields": [field_name],
|
||||
"reasons": [
|
||||
f"{field_name} must specify exactly one number and no ambiguous references (got: '{val}')"
|
||||
],
|
||||
}
|
||||
|
||||
return {
|
||||
"verdict": "complete",
|
||||
"downgraded": False,
|
||||
|
||||
Reference in New Issue
Block a user