feat: register work-issue task routing for role-aware MCP (#139)

- Map work_issue/work-issue to author role and gitea.pr.create in resolver
- Route work-issue sessions through role_session_router before mutations
- Expose work_issue in runtime context task capabilities
- Add work-issue workflow source verifier and canonical routing docs
- Tests for resolver, router, and final-report verifier

Closes #139

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
2026-07-07 09:30:38 -04:00
co-authored by Claude Opus 4.8
parent 7b71113b4d
commit c260ef15fb
9 changed files with 258 additions and 0 deletions
+122
View File
@@ -2113,6 +2113,30 @@ CREATE_ISSUE_FORBIDDEN_CROSS_MODE_FIELDS = (
("Terminal review mutation", ("terminal review mutation",)),
)
WORK_ISSUE_WORKFLOW_MARKERS = (
"workflows/work-issue.md",
"work-issue.md",
)
WORK_ISSUE_TASK_MARKERS = (
"work-issue",
"work issue",
)
WORK_ISSUE_FORBIDDEN_CROSS_MODE_FIELDS = (
("Selected PR", ("selected pr",)),
("Review decision", ("review decision",)),
("Merge result", ("merge result",)),
("Merge preflight", ("merge preflight",)),
("Already-landed gate", ("already-landed gate",)),
("Pinned reviewed head", ("pinned reviewed head",)),
("Terminal review mutation", ("terminal review mutation",)),
("Duplicate search terms", ("duplicate search terms",)),
("Issues created", ("issues created",)),
("Issues skipped as duplicates", ("issues skipped as duplicates",)),
("Requested issue task", ("requested issue task",)),
)
def _handoff_section_lines(report_text):
"""Return the lines of the Controller Handoff section, or None."""
@@ -3244,6 +3268,104 @@ def assess_create_issue_workflow_source(report_text: str) -> dict:
}
def assess_work_issue_workflow_source(report_text: str) -> dict:
"""#139: work-issue reports must cite the canonical workflow file."""
lower = (report_text or "").lower()
reasons = []
if not any(marker in lower for marker in WORK_ISSUE_WORKFLOW_MARKERS):
reasons.append(
"work-issue report missing workflow source "
"(workflows/work-issue.md)"
)
if not any(marker in lower for marker in WORK_ISSUE_TASK_MARKERS):
reasons.append(
"work-issue report missing task mode declaration (work-issue)"
)
return {
"complete": not reasons,
"downgraded": bool(reasons),
"reasons": reasons,
}
def assess_work_issue_mode_isolation(report_text: str) -> dict:
"""#139: reject review-merge/create-issue handoff fields in work-issue runs."""
section = _handoff_section_lines(report_text)
reasons = []
if section is None:
return {
"complete": True,
"downgraded": False,
"reasons": [],
}
labels = []
for line in section:
stripped = line.strip().lstrip("-*").strip()
if ":" in stripped:
labels.append(stripped.split(":", 1)[0].strip().lower())
for name, aliases in WORK_ISSUE_FORBIDDEN_CROSS_MODE_FIELDS:
if any(
label.startswith(alias)
for label in labels
for alias in aliases
):
reasons.append(
f"work-issue handoff must not include cross-mode field "
f"'{name}'"
)
legacy_workspace = any(
label.startswith("workspace mutations") for label in labels
)
precise_categories = any(
label.startswith("file edits by author")
for label in labels
)
if legacy_workspace and not precise_categories:
reasons.append(
"work-issue handoff must use precise mutation categories "
"instead of legacy 'Workspace mutations'"
)
return {
"complete": not reasons,
"downgraded": bool(reasons),
"reasons": reasons,
}
def assess_work_issue_final_report(report_text: str) -> dict:
"""#139: composite verifier for work-issue final reports."""
checks = {
"workflow_source": assess_work_issue_workflow_source(report_text),
"mode_isolation": assess_work_issue_mode_isolation(report_text),
}
reasons = []
downgraded = False
for name, result in checks.items():
verdict = result.get("verdict")
if verdict in ("missing", "incomplete"):
downgraded = True
reasons.extend(result.get("reasons") or [])
elif result.get("downgraded") or not result.get("complete", True):
downgraded = True
reasons.extend(
f"{name}: {r}" for r in (result.get("reasons") or [])
)
grade = "A" if not downgraded else "downgraded"
return {
"grade": grade,
"downgraded": downgraded,
"checks": checks,
"reasons": reasons,
"complete": not downgraded,
}
def assess_create_issue_mode_isolation(report_text: str) -> dict:
"""#337: reject work-issue/review-merge handoff fields in create-issue runs."""
section = _handoff_section_lines(report_text)