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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user